O hirunewani blog

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

Created at

Vitestでモックが上手く動作しないと相談されたので、その理由などについてまとめた。

Table of Contents

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

公式を読もう。

Q.

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

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経由で差し替えれば良い。

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で使われているメソッドが後から定義されることになるのでエラーが出てしまう。