Vitestのモックとhoisting
起こった問題
vitestで1つの関数について2種類のテストを書いており、それぞれのテストの中で同じモジュールをモックしたらテストが落ちる。片方のテストを削除したらパスする。
モックしたモジュール
export const isLocalDev =
ENV_CLIENT.NEXT_PUBLIC_APP_ENV === "dev" &&
ENV_SERVER.NODE_ENV === "development";
環境によって出力がどう変わるか見たいので以下のようにモックを試みた
describe.concurrent("fileNameUploadedUrlTransformer", () => {
afterEach(() => {
vi.clearAllMocks();
});
it("local環境の場合", () => {
vi.mock("@/common/lib/is-local-dev", () => {
return {
isLocalDev: true,
};
});
const input = 'hoge';
const result = targetMethod(input);
expect(result).toBe('local-result');
});
it("local以外の環境の場合", () => {
vi.mock("@/common/lib/is-local-dev", () => {
return {
isLocalDev: false,
};
});
const input = 'fuga';
const result = targetMethod(input);
expect(result).toBe('other-result');
});
});
関数の実装自体は正しいがテストが落ちる
モックの結果を出力してみると、どうやら後から書いたモックで上書きされている模様
このdiscussionを見るとそれぞれのテストでモジュールをモックするのは一筋縄では行かないようである。そもそも今まで特になにも考えずにvi.mock
を使っていたので、一回しっかり調べてみる。
Substitutes all imported modules from provided path with another module. You can use configured Vite aliases inside a path. The call to vi.mock is hoisted, so it doesn't matter where you call it. It will always be executed before all imports. If you need to reference some variables outside of its scope, you can define them inside vi.hoisted and reference them inside vi.mock.
ESモジュールのstatic import
はモジュール読み込み時点で評価される。ただ、Vitestのモック的にはモック宣言 => モジュール読み込み
の順番で、評価を行って欲しいのでvi.mock
は内部的に処理の順番を入れ替えている。
vi.mockを用いてモックを行おうとしているコード
import { targetMethod } from './target-mothod';
vi.mock("@/common/lib/is-local-dev", () => {
return {
isLocalDev: true,
};
});
describe("fileNameUploadedUrlTransformer", () => {
it("local環境の場合", () => {
const input = 'hoge';
const result = targetMethod(input);
expect(result).toBe('local-result');
});
});
vi.mockが実際に変換しているコード
// モジュールのimport文の前にモック宣言を挿入
vi.mock("@/common/lib/is-local-dev", () => {
return {
isLocalDev: true,
};
});
// static-importをdynamic-importに変換
const { targetMethod } = await import('./target-mothod')
describe("fileNameUploadedUrlTransformer", () => {
it("local環境の場合", () => {
const input = 'hoge';
const result = targetMethod(input);
expect(result).toBe('local-result');
});
});
今回の場合はおそらく巻き上げによって、後半のモックのみが適用されてしまっていた