6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

CatallaxyAdvent Calendar 2022

Day 14

jestとvitestではspyOnの振る舞いが違う?

Last updated at Posted at 2022-12-13

jest.spyOn + esbuild/swcの相性問題を前回 jest.spyOnでTypeError: Cannot redefine property でお話しましたが、今回はjest + ts-jest → vitest移行でハマった話をしようと思います。

TL;DR

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

早速見ていきましょう。問題のコードはこれだ!わんつっすり(言ってみたかっただけ

    // 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に全部書いてもらいたいと思います。

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?