[Jest] 呼び出す関数をconditionalに変えるコードのテストではclearAllMocks()を使おう
こんにちは、CX事業本部の若槻です。
Jestは、Facebookが開発しているJavaScriptのテスティングフレームワークです。
今回は、呼び出す関数をconditionalに変えるコードのJestによるテストでの注意点についてです。
環境
% node --version v12.14.0 % jest --version 26.6.3
モックした関数の呼び出し回数がテスト間でリセットされない
下記のようなmodule_b
の関数を呼び出すmodule_a
があります。引数userType
の値に応じて異なる関数が呼び出されます。
import * as ModuleB from "./module_b"; export type UserType = "manager" | "general"; /** * ユーザーがデータを取得する * @param userType ユーザータイプ */ export const getDataByUser = async (userType: UserType): Promise<string> => { if (userType === "manager") { return await ModuleB.getDataByManager(); } userType === "general"; { return await ModuleB.getDataByGeneralUser(); } };
/** * 管理者ユーザーがデータを取得する */ export const getDataByManager = async (): Promise<string> => { return "data for manager"; }; /** * 一般ユーザーがデータを取得する */ export const getDataByGeneralUser = async (): Promise<string> => { return "data for general"; };
この時module_a
に対する次のようなJestのテストコードを作成しました。module_a
のgetDataByUser()
が引数に応じてmodule_b
の適切な関数を適切な回数呼び出せているかをテストする内容です。
import * as ModuleA from "../module_a"; import * as ModuleB from "../module_b"; describe("module_a", () => { (ModuleB.getDataByManager as jest.Mock) = jest.fn().mockReturnValue("dummy"); (ModuleB.getDataByGeneralUser as jest.Mock) = jest .fn() .mockReturnValue("dummy"); it("By Manager", async (): Promise<void> => { ModuleA.getDataByUser("manager"); //getDataByManagerが1回実行されることを期待 expect(ModuleB.getDataByManager).toHaveBeenCalledTimes(1); expect(ModuleB.getDataByGeneralUser).toHaveBeenCalledTimes(0); }); it("By GeneralUser", async (): Promise<void> => { ModuleA.getDataByUser("general"); //getDataByGeneralUserが1回実行されることを期待 expect(ModuleB.getDataByManager).toHaveBeenCalledTimes(0); expect(ModuleB.getDataByGeneralUser).toHaveBeenCalledTimes(1); }); });
しかしこのテストコードを実行すると下記のようにエラーとなってしまいます。1つ目のBy Manager
ケースはPassしています。しかし2つ目のBy GeneralUser
ケースが、getDataByManager
の呼び出し回数が0
回を期待しているところ実際には1
回呼び出されてFailとなっています。
% jest ./module_a.test.ts FAIL test/module_a.test.ts module_a ✓ By Manager (2 ms) ✕ By GeneralUser (2 ms) ● module_a › By GeneralUser expect(jest.fn()).toHaveBeenCalledTimes(expected) Expected number of calls: 0 Received number of calls: 1 20 | 21 | //getDataByGeneralUser が1回実行されることを期待 > 22 | expect(ModuleB.getDataByManager).toHaveBeenCalledTimes(0); | ^ 23 | expect(ModuleB.getDataByGeneralUser).toHaveBeenCalledTimes(1); 24 | }); 25 | }); at Object.<anonymous> (test/module_a.test.ts:22:38) Test Suites: 1 failed, 1 total Tests: 1 failed, 1 passed, 2 total Snapshots: 0 total Time: 2.225 s Ran all test suites matching /.\/module_a.test.ts/i.
原因、解決
原因はテスト間(it
間)で関数の呼び出し回数がリセットされていなかったためでした。
そこでjestのclearAllMocks()
を使用します。
下記のようにテストの冒頭にafterEach
を使用してテストごとにclearAllMocks()
を呼び出してモックの初期化が行われるようにします。
import * as ModuleA from "../module_a"; import * as ModuleB from "../module_b"; describe("module_a", () => { afterEach(() => { jest.clearAllMocks(); }); (ModuleB.getDataByManager as jest.Mock) = jest.fn().mockReturnValue("dummy"); (ModuleB.getDataByGeneralUser as jest.Mock) = jest .fn() .mockReturnValue("dummy"); it("By Manager", async (): Promise<void> => { ModuleA.getDataByUser("manager"); //getDataByManagerが1回実行されることを期待 expect(ModuleB.getDataByManager).toHaveBeenCalledTimes(1); expect(ModuleB.getDataByGeneralUser).toHaveBeenCalledTimes(0); }); it("By GeneralUser", async (): Promise<void> => { ModuleA.getDataByUser("general"); //getDataByGeneralUserが1回実行されることを期待 expect(ModuleB.getDataByManager).toHaveBeenCalledTimes(0); expect(ModuleB.getDataByGeneralUser).toHaveBeenCalledTimes(1); }); });
上記修正後のテストコードであればいずれのテストケースもパスするようになりました。
% jest ./module_a.test.ts PASS test/module_a.test.ts module_a ✓ By Manager (2 ms) ✓ By GeneralUser Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 0 total Time: 3.148 s Ran all test suites matching /.\/module_a.test.ts/i.
おわりに
呼び出す関数をconditionalに変えるコードのJestによるテストでの注意点についてでした。
Jestにはテストを便利に実施するためのメソッド他にもまだまだたくさんあるので使いこなしていきたいです。
以上