vitest icon indicating copy to clipboard operation
vitest copied to clipboard

`vi.mock()` doesn't work if module is already imported

Open ocavue opened this issue 6 months ago • 8 comments

Describe the bug

When I run a test using the following mock code:

import { vi } from "vitest";

vi.mock("./random", () => {
  return {
    randomString: () => "__MOCK__",
  };
});

Vitest in browser mode isn't functioning properly.

I found a section in the documentation discussing Spying on browser mode, but I'm still unsure how to resolve my use case after reading it.

Reproduction

Git repo: https://github.com/issueset/vitest-mock-browser-mode

Reproduction steps:

❯ git clone https://github.com/issueset/vitest-mock-browser-mode
❯ cd vitest-mock-browser-mode
❯ pnpm install
❯ pnpm run test

> @vitest/example-test@ test /private/tmp/vitest-mock-browser-mode
> vitest run

 RUN  v3.2.4 /private/tmp/vitest-mock-browser-mode

 ✓ src/without-mock.test.ts (1 test) 1ms
 ✓ src/with-mock.test.ts (1 test) 1ms

❯ pnpm run test:browser

> @vitest/example-test@ test:browser /private/tmp/vitest-mock-browser-mode
> vitest run --config=vitest.browser.config.ts

 RUN  v3.2.4 /private/tmp/vitest-mock-browser-mode

Port 63315 is in use, trying another one...
 ❯  chromium  src/with-mock.test.ts (1 test | 1 failed) 3ms
   × randomString 2ms
     → expected '43587017925835303' to be '__MOCK__' // Object.is equality
 ✓  chromium  src/without-mock.test.ts (1 test) 0ms

 FAIL   chromium  src/with-mock.test.ts > randomString
AssertionError: expected '43587017925835303' to be '__MOCK__' // Object.is equality

System Info

System:
    OS: macOS 15.5
    CPU: (8) arm64 Apple M2
    Memory: 66.75 MB / 24.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 20.19.3 - /opt/homebrew/opt/node@20/bin/node
    Yarn: 1.22.22 - /opt/homebrew/bin/yarn
    npm: 10.8.2 - /opt/homebrew/opt/node@20/bin/npm
    pnpm: 10.11.0 - /opt/homebrew/bin/pnpm
    bun: 1.2.15 - /opt/homebrew/bin/bun
  Browsers:
    Chrome: 137.0.7151.122
    Safari: 18.5
    Safari Technology Preview: 26.0
  npmPackages:
    @vitest/browser: ^3.2.4 => 3.2.4
    playwright: ^1.53.2 => 1.53.2
    vite: ^7.0.0 => 7.0.0
    vitest: ^3.2.4 => 3.2.4

Used Package Manager

npm

Validations

ocavue avatar Jul 02 '25 12:07 ocavue

Does it work if you move the vi.mock inside the test file?

- import "./random.mock";
import { randomString } from "./random";
- import { test, expect } from "vitest";
+ import { vi, test, expect } from "vitest";

+ vi.mock("./random", () => {
+   return {
+     randomString: () => "__MOCK__",
+   };
+ });

test("randomString", () => {
  expect(randomString()).toBe("__MOCK__");
});

AriPerkkio avatar Jul 02 '25 12:07 AriPerkkio

Does it work if you move the vi.mock inside the test file?

Yes, it works! I'm not quite sure why, though. Is this the expected behavior?

ocavue avatar Jul 02 '25 13:07 ocavue

Does it work if you move the vi.mock inside the test file?

Yes, it works! I'm not quite sure why, though. Is this the expected behavior?

Yes, the mock needs to be registered before the browser starts fetching modules. If you only have static imports, the browser will always fetch them in parallel. vi.mock on the other hand makes it so every import becomes dynamic and we load files one by one.

sheremet-va avatar Jul 03 '25 12:07 sheremet-va

If you only have static imports, the browser will always fetch them in parallel. vi.mock on the other hand makes it so every import becomes dynamic and we load files one by one. Thanks for your response! If I change my imports to dynamic imports, as shown below, then the browser mode works perfectly!

-import "./random.mock";
-import { randomString } from "./random";
 import { test, expect } from "vitest";

+await import("./random.mock");
+const { randomString } = await import("./random");

 test("randomString", () => {
   expect(randomString()).toBe("__MOCK__");
 });

Do you think it's reasonable to convert all imports to dynamic in a test file when using browser mode, even if the test file doesn't directly include vi.mock?

ocavue avatar Jul 05 '25 23:07 ocavue

Do you think it's reasonable to convert all imports to dynamic in a test file when using browser mode, even if the test file doesn't directly include vi.mock?

Vitest does this automatically if there is vi.mock or vi.hoisted in the file. I don't think it's very ergonomic to do it manually.

sheremet-va avatar Jul 06 '25 07:07 sheremet-va

I'm running into the same issue. I want to mock a module for all tests, so adding the mock to every test file and also forcing every other developer in the team to do that is not a feasible solution. Is there another option?

quirk0o avatar Jul 22 '25 15:07 quirk0o

I'm running into the same issue. I want to mock a module for all tests, so adding the mock to every test file and also forcing every other developer in the team to do that is not a feasible solution. Is there another option?

Just add vi.mock to your setup file

sheremet-va avatar Jul 22 '25 15:07 sheremet-va

I'm running into the same issue. I want to mock a module for all tests, so adding the mock to every test file and also forcing every other developer in the team to do that is not a feasible solution. Is there another option?

Just add vi.mock to your setup file

That's what I did but it seems it only works for some of the tests. Adding the vi.mock directly to the other tests does seem to help but like I said, that's not a very maintainable solution.

quirk0o avatar Jul 22 '25 15:07 quirk0o