vitest-dev / vitest-dev/vitest

[Updated] vi.mock ignored when mocked module is pre-loaded transitively via setupFiles

Open
#10,104 9 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

pending triage
Dominant language
TypeScript
Stars
17.1k
Forks
2k
Avg merge
1d 22h
Merged PRs (30d)
94

Description

Really sorry, updated the current one again. Thank god Claude could reproduce it without me making mistakes.

Describe the bug

I closed the previous topic (https://github.com/vitest-dev/vitest/issues/10089) because the description was misleading and there was no reproduction link.

vi.mock ignored when mocked module is pre-loaded transitively via setupFiles

Description

vi.mock is silently ignored when the module being mocked has already been loaded into the module cache by setupFiles — transitively through a barrel (index.ts).

vi.mock is hoisted and registered before the test runs, but the mock factory never intercepts the module because it was already executed and cached during the setupFiles phase.

Reproduction

Minimal repo: https://github.com/...

File structure:

src/
  setup.ts
  mocks/server.ts       ← imports '../ui' (forces barrel execution)
  ui/
    consts.ts           ← exports SORT
    Simple.tsx          ← imports @mui/material/Box
    index.ts            ← barrel: re-exports Simple + SORT
    Simple.test.tsx     ← vi.mock broken ❌
    Simple.doMock.test.tsx  ← workaround ✅

setup.ts:

import { beforeAll, afterEach, afterAll } from 'vitest';
import { server } from './mocks/server';

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

mocks/server.ts:

import { setupServer } from 'msw/node';
import '../ui'; // forces barrel → Simple.tsx → @mui/material/Box into cache

export const server = setupServer();

ui/index.ts:

export { Simple } from './Simple';
export { SORT } from './consts';

ui/Simple.tsx:

import Box from '@mui/material/Box';

export function Simple() {
  return <Box><p>simple</p></Box>;
}

ui/Simple.test.tsx (broken):

import { render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { Simple } from './Simple';

vi.mock('@mui/material/Box', () => ({
  default: ({ children }: React.PropsWithChildren) => (
    <div data-testid='box'>{children}</div>
  ),
}));

describe('vi.mock (broken)', () => {
  it('fails — real MuiBox renders instead of mock', () => {
    render(<Simple />);
    expect(screen.getByTestId('box')).toBeInTheDocument(); // ❌
  });
});

Expected behavior

vi.mock('@mui/material/Box', ...) intercepts the module and the test passes.

Actual behavior

The real @mui/material/Box renders (class="MuiBox-root"). The mock is ignored because Box was already executed and cached during setupFiles before vi.mock could intercept it.

Workaround

// ui/Simple.doMock.test.tsx
vi.resetModules(); // clear the pre-loaded cache
vi.doMock('@mui/material/Box', () => ({
  default: ({ children }: React.PropsWithChildren) => (
    <div data-testid='box'>{children}</div>
  ),
}));

it('passes', async () => {
  const { Simple } = await import('./Simple'); // fresh load gets the mock
  render(<Simple />);
  expect(screen.getByTestId('box')).toBeInTheDocument(); // ✅
});

Vitest version

4.1.2

Additional context

The bug only manifests when the module being mocked is loaded transitively through a barrel during setupFiles. Direct imports in setupFiles from the mocked module itself exhibit the same issue, but this case is particularly surprising because the test file imports Simple directly (not via the barrel) and there is no visible connection between setupFiles and the mock target.

Reproduction

https://github.com/Ghostblad3/vitest-bug

System Info
System:
    OS: Linux 6.6 Ubuntu 24.04.3 LTS 24.04.3 LTS (Noble Numbat)
    CPU: (14) x64 Intel(R) Core(TM) Ultra 7 265U
    Memory: 12.64 GB / 15.35 GB
    Container: Yes
    Shell: 5.2.21 - /bin/bash
  Binaries:
    Node: 20.19.2 - /home/igris/.nvm/versions/node/v20.19.2/bin/node
    Yarn: 1.22.22 - /home/igris/.nvm/versions/node/v20.19.2/bin/yarn
    npm: 10.8.2 - /home/igris/.nvm/versions/node/v20.19.2/bin/npm
    pnpm: 10.29.2 - /mnt/c/nvm4w/nodejs/pnpm
    bun: 1.3.5 - /home/igris/.bun/bin/bun
  npmPackages:
    @vitejs/plugin-react: ~5.1.4 => 5.1.4 
    vite: ^8.0.3 => 8.0.3 
    vitest: ~4.1.2 => 4.1.2
Used Package Manager

pnpm

Validations

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the linked Ghostblad3/vitest-bug reproduction, especially setup.ts, mocks/server.ts, ui/index.ts, ui/Simple.tsx, and the broken and workaround test files. Run the reproduction and compare vi.mock with vi.resetModules plus vi.doMock. Done means the standard vi.mock test intercepts @mui/material/Box even when it was loaded transitively through setupFiles.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript, vite
Domain
testing-qa, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.