JestでTypeScriptのテストを書く(Step4:StubとMockとSpy)

## はじめに
表題通り、JestでTypeScriptのテストを書きます。
下記StepでJestの学習を進めていきます

[Step1: Jest(環境構築から実行まで)](https://qiita.com/koji0705/items/258166f4f281efbb2c46)
[Step2: Jest(Matcherを利用する)](https://qiita.com/koji0705/items/2049d581fe7d343d58d3)
[Step3: Jest(API検証)](https://qiita.com/koji0705/items/1f7099600a97f3a9b328)
[Step4: Jest(StubとMockとSpy)](https://qiita.com/koji0705/items/191a48e51b9eebb81db1)←今回はここ

## StubとMockとSpy
Stub、Mock、Spyの関係性を整理します。
※MockやStubに関しては、記事によって少し定義が異なりました。
　あくまで私の理解で記載させていただきます🙇‍♂️

### Stub
Stubは固定値やシンプルな応答を提供するために用いる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/b5bf2ba1-e645-f5e5-39b1-487dfa4738b7.png)

:::note info
スタブはテスト対象のプロダクトコードが他のプロダクトコードに依存する場合に用いる。
固定値を返却することでテストを実施しやすくするためのもの。
※Java等であればビルドパスを変更する事で向き先は容易に変更できます。
ただ、JavaScriptだとプロダクトコードを直接書き変えないといけないので、あまり使われない？？
:::

### Mock
Stubは挙動を完全に制御し、複雑な検証を行うために用いる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/2861c3bb-b542-16f9-0af4-53ca689ba9b1.png)

:::note info
MockはStubの機能に加えて、呼び出し先の関数を変更できます。
:::

### Spy
Spyは既存の関数やメソッドの呼び出しを監視するために使用する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/7bb825e3-3bd6-b237-e155-74ef934b6a9a.png)

:::note info
Spyは呼び出されしを監視できます。そもそもcallされたのかどうかの細かい検証が実施できる。
また、Spyでは返却値も返せるため、Mockの機能も兼ねてます。
:::

## ソースコード
MockとSpyのテストコードを書き直します。

### Mock
```ts:src/__tests__/mock.test.ts
// 例: 関数をモックして、特定の値を返すように設定する
const mockFunction = jest.fn();
mockFunction.mockReturnValue('mocked value');

test('mock test', () => {
  expect(mockFunction()).toBe('mocked value');
  expect(mockFunction).toHaveBeenCalledTimes(1);
});

```

```shell:実行結果
~/develop/react/jest_ts$ yarn test src/__tests__/mock.test.ts
yarn run v1.22.19
$ jest src/__tests__/mock.test.ts
ts-jest[config] (WARN) message TS151001: If you have issues related to imports, you should consider setting `esModuleInterop` to `true` in your TypeScript configuration file (usually `tsconfig.json`). See https://blogs.msdn.microsoft.com/typescript/2018/01/31/announcing-typescript-2-7/#easier-ecmascript-module-interoperability for more information.
 PASS  src/__tests__/mock.test.ts
  ✓ mock test (2 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.684 s
Ran all test suites matching /src\/__tests__\/mock.test.ts/i.
✨  Done in 1.67s.
```

### Spy
```ts:src/__tests__/spy.test.ts
const myObject = {
  myMethod() {
    // オリジナルの実装
    return 'custom value';
  },
};

describe('myObject tests', () => {
  let spy;

  beforeEach(() => {
    // myMethodにスパイを設定
    spy = jest.spyOn(myObject, 'myMethod');

    // スパイを使って返却値を設定
    spy.mockReturnValue('custom value');
  });

  afterEach(() => {
    // スパイをリセットして元の実装に戻す
    spy.mockRestore();
  });

  // テスト
  it('should call myMethod and return custom value', () => {
    expect(myObject.myMethod()).toBe('custom value');
    expect(spy).toHaveBeenCalled();
  });
});

```

```shell:実行結果
~/develop/react/jest_ts$ yarn test src/__tests__/spy.test.ts
yarn run v1.22.19
$ jest src/__tests__/spy.test.ts
ts-jest[config] (WARN) message TS151001: If you have issues related to imports, you should consider setting `esModuleInterop` to `true` in your TypeScript configuration file (usually `tsconfig.json`). See https://blogs.msdn.microsoft.com/typescript/2018/01/31/announcing-typescript-2-7/#easier-ecmascript-module-interoperability for more information.
 PASS  src/__tests__/spy.test.ts
  myObject tests
    ✓ should call myMethod and return custom value (1 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.643 s, estimated 1 s
Ran all test suites matching /src\/__tests__\/spy.test.ts/i.
✨  Done in 1.03s.
```