症状
以下の状況下で、意図したようにモックが作成/適用されない。
- テスト対象が async 関数で複数テストを行う時
- それぞれのテストで異なるモックを使いたい時
例えば以下のような時に、Case1, Case2 でそれぞれモックを定義しているにもかかわらず、Case2 のモックが使用される問題。
class MockTarget {
public async func(str: string): Promise<string> {
let editedStr: string = str;
/* do something */
return editedStr;
}
}
class UsingMockTarget {
public async func(str: string): Promise<string> {
const mockTarget: MockTarget = new MockTarget();
return await mockTarget.func(str);
}
}
jest.mock('./MockTarget'); // パスを指定
const Mock: jest.Mock = MockTarget as unknown as jest.Mock; // TypeScriptでは型変換する必要がある
test('Case1', async () => {
/* Mock 生成 */
Mock.mockReset();
Mock.mockImplementation(() => {
return {
func: async (str:string) => {
if (str == "test") {
return "test";
}
else {
return "";
}
}
};
});
/* Test実行 */
const usingMockTarget: UsingMockTarget = new UsingMockTarget();
return expect(usingMockTarget.func("test")).resolves.toMatch("test");
});
test('Case2', async () => {
/* Mock 生成 */
Mock.mockReset();
Mock.mockImplementation(() => {
return {
func: async(str:string) => {
if (str == "err") {
throw new Error();
}
else {
return "";
}
}
};
});
/* Test実行 */
const usingMockTarget: UsingMockTarget = new UsingMockTarget();
return expect(usingMockTarget.func("err")).rejects.toThrow();
});
原因
単純な話で、 await によって非同期的にテストが進んでしまうがために、 Case1 のテストが終了する前に Case2 のテストが開始されてモックが上書きされてしまう。
test自体をawait で駆動できれば良いのだが、無理っぽい。少し触った限りでは async 関数のテストをする時には Mock を入れ替えるのは難しそう。
全テストで利用可能な Mock を作らざる終えないが、込み入ったテストをする際に不自由しそう。何か解決策があるのだろうか?
jest.mock('./MockTarget'); // パスを指定
const Mock: jest.Mock = MockTarget as unknown as jest.Mock; // TypeScriptでは型変換する必要がある
/* 全てのテストで利用できるような汎用的なモック */
Mock.mockImplementation(() => {
return {
func: async (str:string) => {
if (str == "test") {
return "test";
}
else if (str == "err") {
throw new Error();
}
else {
return "";
}
}
};
});
test('Case1', async () => {
/* Test実実行 */
const usingMockTarget: UsingMockTarget = new UsingMockTarget();
return expect(usingMockTarget.func("test")).resolves.toMatch("test");
});
test('Case2', async () => {
/* Test実実行 */
const usingMockTarget: UsingMockTarget = new UsingMockTarget();
return expect(usingMockTarget.func("err")).rejects.toThrow();
});