jest.spyOn + esbuild/swcの相性問題を前回 jest.spyOnでTypeError: Cannot redefine property でお話しましたが、今回はjest + ts-jest → vitest移行でハマった話をしようと思います。
TL;DR
- https://github.com/vitest-dev/vitest/issues/1329
- spyOnの振る舞いが違うのではなく、今回もesm→cjs環境におけるトランスパイラの違い
- クラスにするか、ファイルを分けるか、諦める
同一ファイルの別関数をモックできない
早速見ていきましょう。問題のコードはこれだ!わんつっすり(言ってみたかっただけ
// example.ts
export const message = () => {
return 'hi';
};
export const greet = () => {
return message();
};
// example.test.ts
import * as example from './example';
vi.spyOn(example, 'message').mockImplementationOnce(() => 'hello');
it('spyon', () => {
expect(example.greet()).toBe('hello');
});
このテストjest + ts-jestなら問題なく通ります。しかしながらvitestだと example.greet()
は hi
を返却するためコケます。期待に反して message
をモックできていません。
ちなみにjest + esbuild/swcだと前回お話したように TypeError: Cannot redefine property
になります。
何故か
https://github.com/vitest-dev/vitest/issues/1329#issuecomment-1129708816 に回答がありました。
This is not about
spyOn
, but about how babel transforms typescript files.
コメントでvitest(esbuild)がどうトランスパイルしモックできない理由を書いていますが、tsc/babelのesm → cjsトランスパイル結果と比較してみたいと思います。
tsc v4.9.4 (babelも大体同じ)
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.greet = exports.message = void 0;
const message = () => {
return 'hi';
};
exports.message = message;
const greet = () => {
return (0, exports.message)(); // exports.messageをコールしてる
};
exports.greet = greet;
esbuild v0.16.4 (swcも大体同じ)
$ /private/tmp/esbuild/node_modules/.bin/esbuild --format=cjs index.ts
...省略
var esbuild_exports = {};
__export(esbuild_exports, {
greet: () => greet,
message: () => message
});
module.exports = __toCommonJS(esbuild_exports);
const message = () => {
return "hi";
};
const greet = () => {
return message(); // standaloneのmessageをコールしている
};
greetに注目してもらうと良くわかりますが、tsc/babelはexports.messageをコールしているのに対し
esbuild/swcではstandaloneのmessageをコールしています。spyOnはjestだろうがvitestだろうがexportされたものをモックに置き換えるだけなので、後者のケースではモックのしようがないことがわかります。
じゃあどうするか
モックしたくなる対象として外部依存が多いと思います。なので引き続きモックが必要であればそれはファイルを分けたほうが適しているものだと考えて良いと思います。あとはgithubのコメントにもあったようにクラスにするのも一つの方法だと思いますが、モックのためにそこまでという気がします。
おわりに
事業系のタスクをこなす日々で、こういった事象はとりあえず動くからいいやの状態にして回避することが多いですが、記事の題材とすると調べるモチベーションも湧くので内容の濃さはさておき良い機会だと思います。これからも知的欲求には従順でいたい。後一つ記事を担当するので今度は流行りのchatGPTに全部書いてもらいたいと思います。