O hirunewani blog

VitestのspyOnは同一ファイルの別関数をモックできない

Created at

VitestのspyOnは、Jestなどと異なり同一ファイルの別関数をモックできない。ただし、これはVitestのスコープ外の問題であり、Jestなどでも同条件であれば同様の制限が発生する。

vi.spyOnとは

vi.spyOnを用いると、vi.mockと異なり部分的なモックが可能。そのためプライベートメソッドやプリミティブなメソッドをモックすることもできる。

jest.spyOn(window, "fetch").mockImplementation((...args) => {
  console.warn("window.fetch is not mocked for this call", ...args);
  throw new Error("This must be mocked!");
});

spyと付いているが、返り値はvi.fn()であり、スパイではなくモックとして使われていることが多い。

同一ファイルの別関数をモックできない

次のようなコードを実行すると TypeError: Cannot redefine property エラーが出る。

import * as example from "./example";

vi.spyOn(example, "message").mockImplementationOnce(() => "hello");

it("test", () => {
  expect(example.greet()).toBe("hello");
});

Jestで同一のコードを記述すると問題なく実行できる。正確には、Jestでもesbuild/swcを利用する場合、同様のエラーが出る。

VitestとJestでなぜ違うか

esbuild/swcと、tsc/babelのesm→cjs変換方法の違いによって発生している。tsc/babelではexports.someMethodを呼ぶのに対して、esbuild/swcでは、standaloneなメソッドをコールするため、同一ファイルにある別関数をモックできなくなる。

Vitest spyOn has different behaviour than jest’s · Issue #1329 · vitest-dev/vitest

どうすればいいか

これはVitestの問題であるとは言い難く、そもそも同一ファイルの別関数をモックすることは、テストの設計上あまり推奨されないため、メソッドを別ファイルに分けることを推奨する。

全く推奨しないが、classやオブジェクトにモックするものをまとめるという手段も存在する。