Closed10

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');
  });
});
ゆーけんゆーけん

https://vitest.dev/api/vi.html#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');
  });
});
ゆーけんゆーけん

今回の場合はおそらく巻き上げによって、後半のモックのみが適用されてしまっていた

このスクラップは20日前にクローズされました