jestでmock系のtips集

# はじめに
jestネタです。
何番煎じかわからないtipsもありますが、自分が引っかかったところをまとめたものです。
jest+typescript+reactを想定してます

# moment.jsのモック化
```moment_mock.test.ts
const mockDate = jest.spyOn(Date, 'now');

beforeEach(() => {
  // 当日日付の固定化
  mockDate.mockImplementation(() => {
    return new Date(2022, 2, 16, 0, 0, 0).getTime();
  });
});
```

# 外部ファイルのモック化
```external_library.test.ts
import * as HogeApi from '../external/HogeApi';

const mockHogeApi = jest.spyOn(HogeApi, 'default');
beforeEach(() => {
    mockHogeApi.mockReset();
});
```

# window.locationのモック化

```location_mock.test.ts
beforeEach(() => {
  window.history.pushState({}, 'Page Title:test', '/route/path');
});
```
# new Audio().play()のモック化

```audio_mock.test.ts
const mockPlay = jest.spyOn(window.HTMLMediaElement.prototype, 'play');
beforeEach(() => {
    mockPlay.mockReset();
});
```

# 非同期のテスト
```async.test.ts
it('snapshot', (done) => {
  execAsyncFunction();
  setTimeout(() => {
    try {
      expect(mockAsyncApi).toBeCalled();
      done();
    } catch (e) {
      done(e);
    }
  }, 300);
});
```

# 繰り返しテスト
```each.test.ts
it.each`
  input | expected
  ${0}  | ${'firstActual'}
  ${1}  | ${'secondActual'}
`('each test input=$input',
  ({ input, expected }) => {
    const actual = eachFunction();
    expect(actual).toEqual(expected);
  }
);
```

## 繰り返し+非同期のテスト
```each_async.test.ts
it.each`
  input | expected
  ${0}  | ${'firstActual'}
  ${1}  | ${'secondActual'}
`('each async test input=$input',
  ({ input, expected }, done: jest.DoneCallback) => {
    eachAsyncFunction();
    setTimeout(() => {
      try {
        expect(mockAsyncApi).toBeCalled();
        expect(mockAsyncApi.mock.calls[0][0]).toEqual(expected);
        done();
      } catch (error) {
        done(error);
      }
    }, 300);
  }
);
```

# jqueryのモック化
- プロダクトコード中のjquery関数をモック化したい場合
```product.ts
const $ = require('jquery');

const parent = $(e).parents('tr');
```

```jquery_mock.test.ts
jest.mock('jquery', () => {
  const m$ = {
    // jQuery関数のモック化
    parents: jest.fn().mockReturnValue({}),
  };
  return jest.fn(() => m$);
});

it('test',() => {
  // parentsモックの返却値を変更する
  const mockAdd = jest.fn();
  const $ = require('jquery');
  ($('').parents as jest.Mock).mockReturnValue([
    { classList: { add: mockAdd } },
  ]);

  callFunction();

  expect(mockAdd).toBeCalled();
  // parentsモックそのものがcallされたかの検証
  expect($().parents).toBeCalled();
});

afterEach(()=>{
  // 初期化したいなら
  ($('').parents as jest.Mock).mockReset();
})
```

# Math.randomのモック化
```random_mock.test.ts
beforeEach(() => {
    jest.spyOn(global.Math, 'random').mockReturnValue(0.123456789);
});
```

# Modal(react-modal)の描画元を固定化
- react-modalに関するwarningを解消する
```
console.error
  Warning: react-modal: App element is not defined. Please use `Modal.setAppElement(el)` or set `appElement={el}`. This is needed so screen readers don't see main content when modal is opened. It is not recommended, but you can opt-out by setting `ariaHideApp={false}`.
```
```react-modal_mock.test.ts
beforeEach(() => {
    Modal.setAppElement(document.createElement('div'));
});
```
# Link(react-router)のモック化
- Linkが含まれるコンポーネントをshallowする時のエラー回避
```
The above error occurred in the <Link> component:
 ...
Error: Uncaught [Error: useHref() may be used only in the context of a <Router> component.]
 ...
```

```link_mock.test.ts
import { MemoryRouter } from 'react-router-dom';

shallow(
<MemoryRouter>
    <Component />
</MemoryRouter>
);
```

# 参考

- [Mocking moment with Jest](https://elnathsoft.pl/mocking-moment-with-jest/?utm_source=rss&utm_medium=rss&utm_campaign=mocking-moment-with-jest)
- [Jest で audio をモックする](https://dhate.hatenablog.com/entry/2021/08/04/110531)