Closed8

jest mock 調査まとめ

ハトすけハトすけ

jestのモックの基本 モック関数と引数や呼ばれた回数の記録(sinonで言うところのspy機能)

基本はモック関数であるjest.fn を利用する。

jest.fn() の返り値自体にmock というプロパティがあり、それにアクセスすることで、何回呼ばれたとか、引数の値はなんだったのかとかわかる。

const mockCallback = jest.fn(x => 42 + x);
forEach([0, 1], mockCallback);

// The mock function is called twice
expect(mockCallback.mock.calls.length).toBe(2);

// The first argument of the first call to the function was 0
expect(mockCallback.mock.calls[0][0]).toBe(0);

// The first argument of the second call to the function was 1
expect(mockCallback.mock.calls[1][0]).toBe(1);

// The return value of the first call to the function was 42
expect(mockCallback.mock.results[0].value).toBe(42);

これらを使いやすくしたカスタムマッチャも存在する。

// The mock function was called at least once
expect(mockFunc).toHaveBeenCalled();
// 同義: expect(mockFunc.mock.calls.length).toBeGreaterThan(0);

// The mock function was called at least once with the specified args
expect(mockFunc).toHaveBeenCalledWith(arg1, arg2);
// 同義: expect(mockFunc.mock.calls).toContainEqual([arg1, arg2]);

// The last call to the mock function was called with the specified args
expect(mockFunc).toHaveBeenLastCalledWith(arg1, arg2);
// 同義: expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1]).toEqual([ arg1, arg2 ]);

// All calls and the name of the mock is written as a snapshot
expect(mockFunc).toMatchSnapshot();
// 同義:
// expect(mockFunc.mock.calls).toEqual([[arg1, arg2]]);
// expect(mockFunc.getMockName()).toBe('a mock name');
ハトすけハトすけ

jestのモックの基本 あらかじめモック関数が返す値を決めておく

先程の jest.fn に対して mockReturnValuemockReturnValueOnceメソッドを呼び出す。

const myMock = jest.fn();
console.log(myMock());
// > undefined

myMock.mockReturnValueOnce(10).mockReturnValueOnce('x').mockReturnValue(true);

console.log(myMock(), myMock(), myMock(), myMock());
// > 10, 'x', true, true

決められた値ではなく、実装自体を渡すことも可能。mockImplementationmockImplementationOnceを利用する。

jest.mock('../foo'); // this happens automatically with automocking
const foo = require('../foo');

// foo is a mock function
foo.mockImplementation(() => 42);
foo();
// > 42
const myMockFn = jest
  .fn()
  .mockImplementationOnce(cb => cb(null, true)) // 1回目返り値
  .mockImplementationOnce(cb => cb(null, false)); // 2回目返り値

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

myMockFn((err, val) => console.log(val));
// > false
ハトすけハトすけ

jestの基本 モジュールをモックする(sinonで言うところのstub機能)

jestでモジュールをモックする方法は大きく2つあります。

  • テストの最初にjest.mockメソッドを利用する。
  • テストの適当な場所でjest.spyOn() メソッドを利用する。

jest.mockは、たとえdescribeないのbeforeで定義しても、テストファイルの冒頭で実行されます。またmockする実装を定義してあげなければ、モジュールは何もしません。

jest.spyOnは、jest.mockと違い、mockする実装を定義してあげなければ、本物のモジュールの実装が利用されます。

ハトすけハトすけ

jest.mock() パターン

ドキュメントに書いてある方法

user.js
import axios from 'axios';

class Users {
  static all() {
    return axios.get('/users.json').then(resp => resp.data);
  }
}

export default Users;
users.test.js
import axios from 'axios';
import Users from './users';

jest.mock('axios');

test('should fetch users', () => {
  const users = [{name: 'Bob'}];
  const resp = {data: users};
  axios.get.mockResolvedValue(resp);

  // or you could use the following depending on your use case:
  // axios.get.mockImplementation(() => Promise.resolve(resp))

  return Users.all().then(data => expect(data).toEqual(users));
});

注意点

  • TypeScriptだとmockResolvedValueでtypeエラーがでる(as jest.Mockでキャストすればいけそう?)。
  • named exportで直接関数返されるとどう実装すればいいかわかない

TypeScriptでnamed exportの方法

こちらのstack overflowを参考にしてください。
https://stackoverflow.com/questions/59035729/how-to-mock-named-exports-in-jest-with-typescript

hoge.ts
export const hogeFunction = () => 'hogehoge';
user.ts
import {hogeFunction} from './hoge';

class Users {
  static all() {
    return hogeFunction();
  }
}

export default Users;
users.test.ts
import Users from './users';

jest.mock('./hoge', () => {
  return {
    hogeFunction: () => 'fuga',
  };
});

test('should fetch fuga', () => {
  return expect(Users.all()).toBe('fuga');
});

TypeScriptでnamed exportし、かつそのメソッド呼び出しをチェックする

hoge.ts
export const hogeFunction = (repeatNum: number): string =>
  'hoge'.repeat(repeatNum);
user.ts
import { hogeFunction } from './hoge';

class Users {
  static all() {
    return hogeFunction(2);
  }
}

export default Users;

users.test.ts
import * as defaultHogeFunction from './hoge';
import Users from './users';

jest.mock('./hoge', () => {
  return {
    // ここで`mockReturnValue`を定義してもなぜか有効にならない。 NOTE: https://stackoverflow.com/questions/46431638/jest-fn-return-value-returns-undefined-when-using-jest-mock
    hogeFunction: jest.fn(),
  };
});

test('should fetch fuga', () => {
  (defaultHogeFunction.hogeFunction as jest.Mock).mockReturnValue('fuga');
  expect(Users.all()).toBe('fuga');
  expect(defaultHogeFunction.hogeFunction).toHaveBeenCalledWith(2);
});

ポイントは

  • import * as 適当な名前 from './hoge'とnamed exportをまとめること
  • jest.mock のなかで、関数を改めて定義しなおすこと
  • その際に、jest.fn を利用することで、あとのコードで引数呼び出しチェックができること
ハトすけハトすけ

jest.spyOn()パターン

ぼくはどちらかというとこちらが好きですね。

users.test.tsファイル以外は同じ実装です。

users.test.ts
import * as defaultHogeFunction from './hoge';
import Users from './users';

let hogeFunctionSpy: jest.SpyInstance<unknown>;

test('should fetch fuga', () => {
  hogeFunctionSpy = jest
    .spyOn(defaultHogeFunction, 'hogeFunction')
    .mockReturnValue('fuga');
  expect(Users.all()).toBe('fuga');
  expect(hogeFunctionSpy).toHaveBeenCalledWith(2);
});

ハトすけハトすけ

個人的に、reactにおけるカスタムフックなどは、関数自体をnamed exportすることが多いので、そのときはspyOnパターンで行こうと思ってます。

ハトすけハトすけ

注意

create-react-appを使っている方へ

どうやら、create-react-appはデフォルトで各テスト後にmockのリセットを行っているみたいで、spyのmockReturnValueを定義したいとき beforeAll ではなく beforeEach で定義しないとうまく動きません。注意してください。

このスクラップは2021/12/01にクローズされました
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy