358文字
2分
編集

Q. 指定されたパスのすべてのモジュールをモックするmockメソッドは巻き上げられる

インポートされるモジュールを丸ごとモックするようなものなので、巻き上げられる・Hoisting されるのは自然に思えるが、把握していないとハマる可能性がある。

公式を読もう。

#Q.

次のようなコードが上手く動作しないと相談された。

jsx
import { describe, expect, vi } from "vitest";
import isValidScope from "./isValidScope";

describe("isValidScope", () => {
  test("tokenとしてパースできれば有効", () => {
    vi.mock("./parseToken", () => ({
      parseToken: () => true,
    }));

    expect(isValidScope("test")).expect(true);
  });

  test("tokenとしてパースできなければ不正", () => {
    vi.mock("./parseToken", () => ({
      parseToken: () => false,
    }));

    expect(isValidScope("test")).expect(false);
  });
});

#Why

すべての vi.mock は巻き上げられ、同一のパスに対して呼ばれていると後に書かれたものが優先されるため、前半のテストが落ちるようになってしまっている。

#A.

次のようにして、mockReturnValue や mockImplementation 経由で差し替えれば良い。

jsx
import { describe, expect, vi } from "vitest";
import isValidScope from "./isValidScope";

const mocks = vi.hoisted(() => {
  return {
    parseToken: vi.fn(),
  };
});
vi.mock("./parseToken", () => {
  return {
    parseToken: mocks.parseToken,
  };
});

describe("isValidScope", () => {
  test("tokenとしてパースできれば有効", () => {
    mocks.parseToken.mockReturnValue(true);

    expect(isValidScope("test")).expect(true);
  });

  test("tokenとしてパースできなければ不正", () => {
    mocks.parseToken.mockReturnValue(false);

    expect(isValidScope("test")).expect(false);
  });
});

vi.hoisted で記述されたものは巻き上げられる。これを使わなければ、vi.mock で使われているメソッドが後から定義されることになるのでエラーが出てしまう。

編集