【Jest】Mockの使い方

目次

はじめに

本記事はJestMockの使い方をまとめました。

環境構築したい場合は以下の記事にまとめておりますので、是非ご参考ください。

Matcher(Assert)の一覧が知りたい人は以下の記事をご覧ください。

Modifierの一覧が知りたい人は以下の記事をご覧ください。

mockFn.getMockName()

mockFn.getMockName()はモック関数の名前を返します。

import { describe, expect, test } from '@jest/globals';

describe('sum module', () => {
    test('adds 1 + 2 to equal 3', () => {
        const mockFn = jest.fn();
        mockFn.getMockName();
        expect(mockFn.getMockName()).toBe("jest.fn()");
    });
});

mockFn.mock.calls

mockFn.mock.callsはモック関数でコールされたすべての引数の配列を返します。

例えば、f(‘arg1’, ‘arg2’)f(‘arg3’, ‘arg4’)で呼ばれるモック関数fは次のような配列を返します。

[
  ['arg1', 'arg2'],
  ['arg3', 'arg4'],
];

mockFn.mock.results

mockFn.mock.resultsはモック関数に対して行われたすべての呼び出しの結果を含む配列を返します。

配列の各要素はオブジェクトで返却され、typeプロパティとvalueプロパティを持ちます。

  • typeプロパティには以下のいずれかが格納されています。
    • return
      • 正常終了したコールを示します。
    • throw
      • 値を返して呼び出しが完了したことを示します。
    • incomplete
      • 呼び出しがまだ完了していないことを示します.
      • モック関数自体の中から結果をテストする場合や、モック関数によって呼び出された関数内からの結果をテストする場合に発生します。
  • valueプロパティには以下が格納されています。
    • スローまたは返された値が含まれています。
    • typeincompleteの場合はundefinedになります。
[
  {
    type: 'return',
    value: 'result1',
  },
  {
    type: 'throw',
    value: {
      /* Error instance */
    },
  },
  {
    type: 'return',
    value: 'result2',
  },
];

mockFn.mock.instances

mockFn.mock.instancesはモック関数のインスタンスを返します。

const mockFn = jest.fn();

const a = new mockFn();
const b = new mockFn();

mockFn.mock.instances[0] === a; // true
mockFn.mock.instances[1] === b; // true

mockFn.mock.contexts

mockFn.mock.contextsはモック関数のすべてのコンテキストを含む配列を返します。

コンテキストは、thisが呼び出されたときに受け取る値です。

const mockFn = jest.fn();

const boundMockFn = mockFn.bind(thisContext0);
boundMockFn('a', 'b');
mockFn.call(thisContext1, 'a', 'b');
mockFn.apply(thisContext2, ['a', 'b']);

mockFn.mock.contexts[0] === thisContext0; // true
mockFn.mock.contexts[1] === thisContext1; // true
mockFn.mock.contexts[2] === thisContext2; // true

mockFn.mock.lastCall

mockFn.mock.lastCallはモック関数でコールされた最後の呼び出しの引数の配列を返します。

関数が呼び出されなかった場合は、 undefinedを返します。

例えば、f(‘arg1’, ‘arg2’)f(‘arg3’, ‘arg4’)で呼ばれるモック関数fは次のような配列を返します。

['arg3', 'arg4'];

mockFn.mockClear()

mockFn.mockClear()は以下のプロパティで返される情報をクリアします。

モック関数のデータをクリーンアップする場合に役立ちます。

  • mockFn.mock.calls
  • mockFn.mock.instances
  • mockFn.mock.contexts
  • mockFn.mock.results

mockFn.mockReset()

mockFn.mockReset()mockFn.mockClear()を行い、モックされた戻り値、実装も削除します。

モック関数を完全にリセットして初期状態に戻したい場合に使用します。

resetMocksの構成オプションを使用して、各テストの前にモックを自動的にリセットできます。

mockFn.mockRestore()

mockFn.mockRestore()mockFn.mockReset()を行い、元の (モックされていない)実装を復元します。

あるテストケースでモック関数を利用して他のテストケースでは本物のモジュールに戻したい場合に使用します。

restoreMocksの構成オプションを使用して、各テストの前にモックを自動的に復元できます。

mockFn.mockImplementation(fn)

mockFn.mockImplementationはモックの実装として使用される関数を返します。

jest.fn(implementation)jest.fn().mockImplementation(implementation)の省略形です。

const mockFn = jest.fn(scalar => 42 + scalar);

mockFn(0); // 42
mockFn(1); // 43

mockFn.mockImplementation(scalar => 36 + scalar);

mockFn(2); // 38
mockFn(3); // 39

クラスのモックとしても使用できます。

module.exports = class SomeClass {
  method(a, b) {}
};
const SomeClass = require('./SomeClass');

jest.mock('./SomeClass');

const mockMethod = jest.fn();
SomeClass.mockImplementation(() => {
  return {
    method: mockMethod,
  };
});

const some = new SomeClass();
some.method('a', 'b');

console.log('Calls to method: ', mockMethod.mock.calls);

mockFn.mockImplementationOnce(fn)

mockFn.mockImplementationOnceはモック関数への1回のコールに対する実装として使用される関数を返します。

複数回のコールが異なる結果を返せるようにチェーンをすることができます。

const mockFn = jest
  .fn()
  .mockImplementationOnce(cb => cb(null, true))
  .mockImplementationOnce(cb => cb(null, false));

mockFn((err, val) => console.log(val)); // true
mockFn((err, val) => console.log(val)); // false

mockImplementationOnceでモック化された実装を使い果たすと、デフォルトの実装が呼び出されます。

const mockFn = jest
  .fn(() => 'default')
  .mockImplementationOnce(() => 'first call')
  .mockImplementationOnce(() => 'second call');

mockFn(); // 'first call'
mockFn(); // 'second call'
mockFn(); // 'default'
mockFn(); // 'default'

mockFn.mockName(name)

mockFn.mockNameはモック関数に名前を設定します。

getMockNameで設定した名前が返却されます。

const j = jest.fn().mockName("mockFn");
console.log(j.getMockName()); // "mockFn"

mockFn.mockReturnThis()

mockFn.mockReturnThisは以下の省略形の関数です。

jest.fn(function () {
  return this;
});

mockFn.mockReturnValue(value)

mockFn.mockReturnValueは以下の省略形の関数です。

jest.fn().mockImplementation(() => value);

mockFn.mockReturnValueOnce(value)

mockFn.mockReturnValueOnceは以下の省略形の関数です。

jest.fn().mockReturnValueOnce(() => value);

mockFn.mockResolvedValue(value)

mockFn.mockResolvedValueは以下の省略形の関数です。

jest.fn().mockImplementation(() => Promise.resolve(value));

mockFn.mockResolvedValueOnce(value)

mockFn.mockResolvedValueOnceは以下の省略形の関数です。

jest.fn().mockImplementationOnce(() => Promise.resolve(value));

mockFn.mockRejectedValue(value)

mockFn.mockRejectedValueは以下の省略形の関数です。

jest.fn().mockImplementation(() => Promise. reject(value));

mockFn.mockRejectedValueOnce(value)

mockFn.mockRejectedValueOnceは以下の省略形の関数です。

jest.fn().mockImplementationOnce(() => Promise. reject(value));

mockFn.withImplementation(fn, callback)

mockFn.withImplementationはコールバックの実行中にモックの実装として一時的に使用する関数を返します。

test('test', () => {
  const mock = jest.fn(() => 'outside callback');

  mock.withImplementation(
    () => 'inside callback',
    () => {
      mock(); // 'inside callback'
    },
  );

  mock(); // 'outside callback'
});

コールバックが非同期の場合、promiseが返されます。

test('async test', async () => {
  const mock = jest.fn(() => 'outside callback');

  // We await this call since the callback is async
  await mock.withImplementation(
    () => 'inside callback',
    async () => {
      mock(); // 'inside callback'
    },
  );

  mock(); // 'outside callback'
});

TypeScriptでの使用方法

以降はTypeScriptでの使用できるモックについて紹介する。

jest.fn.(implementation?)

jest.fnimplementationが渡されると、正しいモックが推論されます。

型の安全性を確保するために、ジェネリクス型の引数を渡すことができます。

import {expect, jest, test} from '@jest/globals';
import type add from './add';
import calculate from './calc';

test('calculate calls add', () => {
  // Create a new mock that can be used in place of `add`.
  const mockAdd = jest.fn<typeof add>();

  // `.mockImplementation()` now can infer that `a` and `b` are `number`
  // and that the returned value is a `number`.
  mockAdd.mockImplementation((a, b) => {
    // Yes, this mock is still adding two numbers but imagine this
    // was a complex function we are mocking.
    return a + b;
  });

  // `mockAdd` is properly typed and therefore accepted by anything
  // requiring `add`.
  calculate(mockAdd, 1, 2);

  expect(mockAdd).toHaveBeenCalledTimes(1);
  expect(mockAdd).toHaveBeenCalledWith(1, 2);
});

jest.Mock<T>

jest.Mockjest.fnの戻り値の型など、モック関数の方を構築します。

import {jest} from '@jest/globals';

const sumRecursively: jest.Mock<(value: number) => number> = jest.fn(value => {
  if (value === 0) {
    return 0;
  } else {
    return value + fn(value - 1);
  }
});

jest.Mocked<Source>

jest.Mockedはモック関数の型定義でラップされた型を返します。

クラス、関数、またはオブジェクトの型は、型引数としてjest.Mockedに渡すことができます。

入力タイプを制限したい場合は以下を使用して制限することが可能です。

  • jest.MockedClass<Source>
  • jest.MockedFunction<Source>
  • jest.MockedObject<Source>
import {expect, jest, test} from '@jest/globals';
import type {fetch} from 'node-fetch';

jest.mock('node-fetch');

let mockedFetch: jest.Mocked<typeof fetch>;

afterEach(() => {
  mockedFetch.mockClear();
});

test('makes correct call', () => {
  mockedFetch = getMockedFetch();
  // ...
});

test('returns correct data', () => {
  mockedFetch = getMockedFetch();
  // ...
});

jest.Replaces<Source>

jest.Replaces<Source>の型定義でラップされた型を返します。

export function isLocalhost(): boolean {
  return process.env['HOSTNAME'] === 'localhost';
}
import {afterEach, expect, it, jest} from '@jest/globals';
import {isLocalhost} from '../utils';

let replacedEnv: jest.Replaced<typeof process.env> | undefined = undefined;

afterEach(() => {
  replacedEnv?.restore();
});

it('isLocalhost should detect localhost environment', () => {
  replacedEnv = jest.replaceProperty(process, 'env', {HOSTNAME: 'localhost'});

  expect(isLocalhost()).toBe(true);
});

it('isLocalhost should detect non-localhost environment', () => {
  replacedEnv = jest.replaceProperty(process, 'env', {HOSTNAME: 'example.com'});

  expect(isLocalhost()).toBe(false);
});

jest.Spied<Source>

スパイされたクラスまたは関数の型を構築します。

import {jest} from '@jest/globals';

export function setDateNow(now: number): jest.Spied<typeof Date.now> {
  return jest.spyOn(Date, 'now').mockReturnValue(now);
}
import {afterEach, expect, jest, test} from '@jest/globals';
import {setDateNow} from './__utils__/setDateNow';

let spiedDateNow: jest.Spied<typeof Date.now> | undefined = undefined;

afterEach(() => {
  spiedDateNow?.mockReset();
});

test('renders correctly with a given date', () => {
  spiedDateNow = setDateNow(1482363367071);
  // ...

  expect(spiedDateNow).toHaveBeenCalledTimes(1);
});

参考

まとめ

この記事ではJestのモックの使い方をまとめました。

是非、いいねやコメントをお願いします。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次