テストケースごとに既存モジュールのモックの返却値を定義する【Jest】

**Jestのモックをテストケースごとに分ける方法が分からない！** と詰まって悩みまくっていました。
という訳で、調べた事の振り返りも兼ねて今回の記事でJestでのモックとテストケースの分け方について書いていきたいです。

# 前提知識

## モック関数について

外部のAPIを利用したり、時間など実行時に返却値が変わるモジュールで、ある値を返したい…という場合に、Jestのモック関数という機能を利用します。

https://jestjs.io/ja/docs/mock-functions

https://qiita.com/shunii/items/64ff8b8c7957d8f8b61a

### モックの返却値のリセット

モックで設定した返却値は、`jest.resetAllMocks()`ですべて初期化できます。
個別に初期化する場合は`.mockReset()`を使用します。

公式ドキュメントには以下のように記載がされています。

>> ### mockFn.mockReset()
>> Does everything that mockFn.mockClear() does, and also replaces the mock implementation with an empty function, returning undefined.
>>The resetMocks configuration option is available to reset mocks automatically before each test.
>
>mockFn.mockClear() が行うすべてのことを行い、さらにモックの実装を undefined を返す空の関数に置き換えます。
>
>> ### jest.resetAllMocks()
>> Resets the state of all mocks. Equivalent to calling .mockReset() on every mocked function.
>> Returns the jest object for chaining.
>
>すべてのモックの状態をリセットします。これは、モック化されたすべての関数に対して .mockReset() を呼び出すのと同等です。チェーンメソッドとして使用できるよう、jest オブジェクトを返します。


要するに、`mockReturnValue()`等で設定したモックの動作や戻り値を消去できます。（呼び出し履歴も）
解説としては以下の記事が丁寧でした。

https://dev.classmethod.jp/articles/jest-the-difference-between-clearallmocks-and-resetallmocks/

:::note info
モックのリセットを行うと、`jest.fn()`の状態に戻るイメージです。
:::

## テストのグループ化について

Jestのテストは`describe()`でグループ化することが出来ます。


```sample.test.ts
// itをグループ化出来る
describe("A機能", () => {
  it("ケース1", async () => {
    // テスト
  });
  it("ケース2", async () => {
    // テスト
  });
});

// describeは複数組み合わせる事が出来る
describe("B機能", () => {
  describe("画面のテスト", () => {
    // 画面のテストケース
  });

  describe("ロジックのテスト", () => {
    // ロジックのテストケース
  });
});
```

テストの可読性や保守性が上がるため、複雑だったり複数の機能を持つテストを書く場合は`describe`で機能や観点ごとにグループ化するとよいです。

# 本題

上記を踏まえて、今回は以下のやり方でテストケースとモックを作成していきます。

## 観点

1. モック対象のモジュールから返却する値ごとにdescribeを作成する
1. describeごとにmockの戻り値を定義する（itごとには行わない）

## テストコード内のモックの定義

テストコード内で以下のようにモックの定義を行います。

1. テストコード先頭でモック化する対象のモジュールを設定する
1. `describe`内の`beforeAll()`内でモックを定義する
    この際、TypeScriptでは`(_対象のモジュール_ as jest.Mock)`と記載する
1. `it`でテストケースを記載
1. `afterAll()`でモックに定義した返却値をリセット

### 実際に返却値を変えたテストコード

sample.tsはランダムなnumberを戻します。

```typescript
import { sample } from "../sample";

jest.mock("../sample", () => ({
  sample: jest.fn(),
}));

describe("グループ1", () => {
  beforeAll(() => {
    (sample as jest.Mock).mockImplementation(() => 1); //　ここでモック返却値を１に
  });

  afterAll(() => {
    jest.resetAllMocks();
  });

  it("ケース1", async () => {
    expect(sample()).toBe(1); // １が取得できる
  });

});

describe("グループ2", () => {
  beforeAll(() => {
    (sample as jest.Mock).mockImplementation(() => 2);//　ここでモック返却値を２に
  });

  afterAll(() => {
    jest.resetAllMocks();
  });

  it("ケース2", () => {
    expect(sample()).toBe(2); // ２が取得できる
  });
});
```

テスト全体の流れとしては以下の図のようになります。

```mermaid
graph TD
    A(開始) --> B(jest.mockでモジュールをモック化)
    B --> C(describe: テストスイートの定義)
    C --> D(beforeAll: モックの設定)
    D --> E(it: 個別のテストケース)
    E -->|describe内のテストをすべて実行| G(afterAll: モックのリセット jest.resetAllMocks)
    G -->|describeがまだ残っている| C
    G -->|テスト終了| I(終了)
```

:::note warn
詳細にグループ化する必要がなく、`it()`の内部でモックを定義したい場合は`afterAll()`を`afterEach()`に変更することで、`it()`ごとにモックの設定を初期化できます。
:::

`jest.fn()`、戻り値を定義した状態、モックのリセットを行ったり来たりするイメージです。

# 最後に（テストコードについて）

テストコードは「**そのコードの仕様**」を書くべき、と教わりました。
コードの仕様を把握するには、何より可読性が大切です。
今後はdescribeによるグループ化やモックを用いて運用しやすいテストを書けるよう頑張りたいです。

少しでも参考になりましたら、いいねを頂けると夜中に喜びの舞を踊ります！
Xもやっているので良かったらフォローお願いします！

https://x.com/miu_y_65535

# その他参考にさせて頂いた記事

https://jestjs.io/ja/docs/mock-function-api