この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは、CX事業本部の若槻です。
Jestは、Facebookが開発しているJavaScriptのテスティングフレームワークです。
今回は、呼び出す関数をconditionalに変えるコードのJestによるテストでの注意点についてです。
環境
% node --version
v12.14.0
% jest --version
26.6.3
モックした関数の呼び出し回数がテスト間でリセットされない
下記のようなmodule_b
の関数を呼び出すmodule_a
があります。引数userType
の値に応じて異なる関数が呼び出されます。
module_a.ts
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();
}
};
module_b.ts
/**
* 管理者ユーザーがデータを取得する
*/
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
の適切な関数を適切な回数呼び出せているかをテストする内容です。
test/module_a.test.ts
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()
を呼び出してモックの初期化が行われるようにします。
test/module_a.test.ts
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にはテストを便利に実施するためのメソッド他にもまだまだたくさんあるので使いこなしていきたいです。
以上