jest.spyOn()の基本的な使い方を確認してみた

2022.06.14

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、CX事業本部 IoT事業部の若槻です。

JavaScriptのテスティングフレームワークJestでは、テスト対象のModuleやFunctionのモックを作成するためのJest Objectがいくつか用意されています。

今回は、そのJest Objectのうちのjest.spyOn()の基本的な使い方を確認してみました。

確認してみた

確認を実施した環境は次の通りです。

npm ls jest typescript --depth=0
hoge-project@0.1.0
├── jest@26.6.3
└── typescript@3.9.10

jest.spyOn(object, methodName)

jest.spyOn(object, methodName)を使用すると、Methodを含んだObjectのmock functionを作成する(=モックする)ことが出来ます。

下記はplayMethodを持つvideoをモックしているテスト例です。

video.ts

export const video = {
  play() {
    return true;
  },
};

test/video.test.ts

import { video } from '../video';

test('plays video true', () => {
  const spy = jest.spyOn(video, 'play');
  const isPlaying = video.play();

  expect(spy).toHaveBeenCalled();
  expect(isPlaying).toBe(true);

  spy.mockRestore();
});

テストを実行すると成功しました。

$ npx jest test/video.test.ts  
 PASS  test/video.test.ts
  ✓ plays video true (2 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        5.011 s
Ran all test suites matching /test\/video.test.ts/i.

またjest.spyOn(object, methodName).mockImplementation(() => customImplementation)のように使用すればMethodを上書きして戻り値を任意の値に指定することもできます。

test/video.test.ts

test('plays video false', () => {
  const spy = jest.spyOn(video, 'play').mockImplementation(() => false);
  const isPlaying = video.play();

  expect(spy).toHaveBeenCalled();
  expect(isPlaying).toBe(false);

  spy.mockRestore();
});

テストを実行すると成功しました。

$ npx jest test/video.test.ts  
 PASS  test/video.test.ts (5.252 s)
  ✓ plays video true (2 ms)
  ✓ plays video false (1 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        5.296 s
Ran all test suites matching /test\/video.test.ts/i.

Methodの上書きは、認証や外部への接続が必要となるMethodのテストで役に立ちそうですね。

jest.fn()との比較

前述したjest.spyOnでのMethodの戻り値の上書きは、jest.fn()を使用した場合にも行えます。

比較のために同じvideojest.fn()でモックしてみます。mockReturnValue()を使用します。

test/video.test.ts

test('plays videos false jest.fn()', () => {
  (video.play as jest.Mock) = jest.fn().mockReturnValue(false);
  const isPlaying = video.play();

  expect(video.play).toHaveBeenCalled();
  expect(isPlaying).toBe(false);
});

テストを実行すると成功しました。

$ npx jest test/video.test.ts          
 PASS  test/video.test.ts (5.449 s)
  ✓ plays video true (2 ms)
  ✓ plays video false (1 ms)
  ✓ plays videos false jest.fn()

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        5.508 s, estimated 6 s
Ran all test suites matching /test\/video.test.ts/i.

jest.spyOn(object, methodName, accessType?)

Methodはgetterまたはsetterのいずれかを指定することができますが、spyOnの第三引数accessTypegetまたはsetを指定すれば、getterとsetterのそれぞれをモックしてテストすることができます。

下記はplayMethodを持つvideoをモックしているテスト例です。

video_and_audio.ts

export const video = {
  // it's a getter!
  get play() {
    return true;
  },
};

export const audio = {
  _volume: 0,
  // it's a setter!
  set volume(value) {
    this._volume = value;
  },
  get volume() {
    return this._volume;
  },
};

test/video_and_audio.test.ts

import { video, audio } from '../video_and_audio';

test('plays video', () => {
  const spy = jest.spyOn(video, 'play', 'get'); // we pass 'get'
  const isPlaying = video.play;

  expect(spy).toHaveBeenCalled();
  expect(isPlaying).toBe(true);

  spy.mockRestore();
});

test('plays audio', () => {
  const spy = jest.spyOn(audio, 'volume', 'set'); // we pass 'set'
  audio.volume = 100;

  expect(spy).toHaveBeenCalled();
  expect(audio.volume).toBe(100);

  spy.mockRestore();
});

テストを実行すると成功しました。

npx jest test/video_and_audio.test.ts
 PASS  test/video_and_audio.test.ts (5.647 s)
  ✓ plays video (3 ms)
  ✓ plays audio

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        5.705 s
Ran all test suites matching /test\/video_and_audio.test.ts/i.

setterやgetterをテストしたい場合はjest.spyOn()が役に立ちそうですね。

おわりに

jest.spyOn()の基本的な使い方を確認してみました。

今までテスト時のモックはjest.fn()を使うことがほとんどでjest.spyOn()には明るくなかったので、今回確認できて良かったです。

以上