jestを完全に理解したい（jest.fn()編）

## はじめに
jestでメソッドをmockする際、フワッとした知識で使ってたので改めてまとめました。


今回はJest でよく使う以下のmock3兄弟について説明していきます。

1. jest.fn()　←今回はこいつについて
2. [jest.SpyOn()](https://qiita.com/only/items/e3c46e6155dc44644111)
3. [jest.mock](https://qiita.com/only/items/07634f917660f2e791b0)

こいつらは似たようなことができるので
どれを使えばいいのかいつもわからなくなります。
今回はjest.fn()について説明していきます。

##  jest.fn()
- 何かを完全にモック化したい場合（その振る舞いを完全に制御したい場合）に使用する。
- コールバック関数のテストや特定の関数がどのように呼び出されるべきかをテストするときに便利です。
このjest.fn()で制御できるのは以下の4つです。
#### 1. 呼び出しの回数を確認
```typescript:例 jest.fn()で呼び出し回数をカウントする
function exampleFunction(fn) {
  fn();
  fn();
}

test('test1', () => {
  const mockFunction = jest.fn();

  // モック関数を直接呼び出します
  mockFunction();

  // モック関数を他の関数の中から呼び出します
  exampleFunction(mockFunction);

  // mockFunctionが呼び出されたことを確認します
  expect(mockFunction).toHaveBeenCalled();

  // mockFunctionが合計で3回呼び出されたことを確認します
  expect(mockFunction).toHaveBeenCalledTimes(3);
});

```
- このようにテストメソッド内で何回呼び出されたかを.toHaveBeenCalledTimesで
取得することができます。（呼び出されたかどうかはが知りたいだけならtoHaveBeenCalled()で確認できます）
- test内で実行しているメソッド（例ではexampleFunction）の中で実行された回数も
カウントしてくれます。しかしその場合はテスト内で作成したjest.fn()の関数をDI(Dependency Injection)する必要があります。exampleFunctionにmockFunctionを渡しているところです。

#### 2. 呼び出しの引数を確認
```typescript:例 jest.fn()で呼び出しの引数を確認する
function exampleFunction(fn) {
  fn('arg1', 'arg2');
}

test('test2', () => {
  const mockFunction = jest.fn();

  // exampleFunction の中でモック関数を呼び出します
  exampleFunction(mockFunction);

  // mockFunction が 'arg1','arg2' という引数で呼び出されたことを確認します
  expect(mockFunction).toHaveBeenCalledWith('arg1', 'arg2');
});

```
- このようにテストメソッド内でどのような引数で呼び出されたかを.toHaveBeenCalledWithで確認することができます。
- 引数がオブジェクトだった場合でも同様に使用できます。（再帰的に中身の要素が同じであるかを比較してくれます。）

#### 3.モック関数の戻り値を指定
```typescript:例 jest.fn()で戻り値を指定する
function exampleFunction(fn) {
  return fn();
}

test('test3', () => {
  const mockFunction = jest.fn();

  // モック関数の戻り値を 'returnValue' と指定します
  mockFunction.mockReturnValue('returnValue');

  // exampleFunction の中でモック関数を呼び出します
  const result = exampleFunction(mockFunction);

  // mockFunction の戻り値が 'returnValue' になっていることを確認します
  expect(result).toBe('returnValue');
});

```
- このようにjest.fn()の返り値をmockReturnValueで指定することができます。
- テストする関数の内部で呼び出される他の関数の結果を制御したいときによく使います
- また以下のようにmockReturnValueOnceを使用することで呼び出し回数ごとの
戻り値を指定することもできます。
```typescript:呼び出し回数ごとの戻り値を指定
test('test4', () => {
  const mockFunction = jest.fn();

  // モック関数が呼び出されるたびに異なる値を返すように設定
  mockFunction.mockReturnValueOnce(10)
              .mockReturnValueOnce(20)
              .mockReturnValue(30)

  // 1回目の呼び出し
  const firstCall = mockFunction();
  expect(firstCall).toBe(10);

  // 2回目の呼び出し
  const secondCall = mockFunction();
  expect(secondCall).toBe(20);

  // 3回目以降の呼び出し
  const thirdCall = mockFunction();
  expect(thirdCall).toBe(30);

  // 4回目以降の呼び出し
  const fourthCall = mockFunction();
  expect(fourthCall).toBe(30);

  // mockFunctionが正確に4回呼び出されたことを確認
  expect(mockFunction).toHaveBeenCalledTimes(4);
});

```
- 上記では.mockReturnValueOnceを2回繋げて1回目と2回目の戻り値を指定しています。3回目以降は.mockReturnValue(30)でデフォルトの戻り値を指定しているので30が戻り値となります。
- mockReturnValue(30)が設定されていない場合は3回目以降の呼び出しではundefinedが返されます。


#### 4.モック関数の実装を指定
```typescript:例 jest.fn()でモック関数の実装を指定する
function exampleFunction(fn) {
  return fn(5, 7);
}

test('test5', () => {
  const mockFunction = jest.fn((num1, num2) => {
    return num1 * num2;
  });

  // exampleFunction の中でモック関数を呼び出します
  const result = exampleFunction(mockFunction);

  // mockFunction の実装が正しく実行され戻り値が 35 になっていることを確認します
  expect(result).toBe(35);

  // mockFunction が引数 (5, 7) で呼び出されたことを確認します
  expect(mockFunction).toHaveBeenCalledWith(5, 7);
});

```
- このようにすることでjest.fn()で実行されるメソッドの実装を指定することができます。
- 依存関数の動作を模倣する場合や、複雑なシナリオをテストする場合に使用します。
- また同様の書き方としてmockImplementationを使用した方法があります。
```typescript:mockImplementaionを使用した方法
// jest.fn() を使用した場合（上記の方法）
const mockFunction = jest.fn((num1, num2) => num1 + num2);

// mockImplementation() を使用した場合
const mockFunction = jest.fn()
mockFunction.mockImplementation((num1, num2) => num1 + num2);
```
- 上記のコードは動作としては同じです。
- 異なる点、使い分けとしてmockImplementationの場合、後から実装を変更することができます。jest.fn()の中に書いた場合は後からの書き換えはできません。
- なのでテストメソッドごとに実装を変更したい時はmockImplementationを使うと便利です。beforeEachで jest.fn() を変数に入れておいて各メソッドで　.mockImplementationを使って実装の書き換えを行うって感じの使い方。



## 終わりに
他の2つも随時まとめていきます。
ご指摘等ありましたらコメントください。