DEV Community

MSakai
MSakai

Posted on

Why your vi.mock in Vitest silently does nothing

You wrote a mock. The test still hits the real module. Nothing in the error message points at why.

import { fetchUser } from './api'
import { vi, test, expect } from 'vitest'

const mockFetch = vi.fn()
vi.mock('./api', () => ({ fetchUser: mockFetch }))  // ReferenceError
Enter fullscreen mode Exit fullscreen mode

What actually happens

vi.mock is hoisted to the top of the file by Vitest's transform, above every import. So by the time the factory runs, mockFetch does not exist yet.

Order at runtime:

  1. vi.mock('./api', factory) — registered first
  2. import { fetchUser } from './api' — resolved, factory invoked
  3. const mockFetch = vi.fn() — too late

The fix

Use vi.hoisted so the mock value is created in the same hoisted phase:

const { mockFetch } = vi.hoisted(() => ({ mockFetch: vi.fn() }))

vi.mock('./api', () => ({ fetchUser: mockFetch }))
Enter fullscreen mode Exit fullscreen mode

Or skip the shared variable entirely and reach for the mocked module inside the test:

import { fetchUser } from './api'

vi.mock('./api')

test('calls the api', async () => {
  vi.mocked(fetchUser).mockResolvedValue({ id: 1 })
  // ...
})
Enter fullscreen mode Exit fullscreen mode

The rule worth memorising

Anything referenced inside a vi.mock factory must be created by vi.hoisted, or defined inside the factory itself.

That one line prevents most of the mocking bugs people file against Vitest.


Want the full picture?

I cover this end-to-end in my Udemy course: Vitest by Example: 50 Hands-on Drills for TypeScript Unit Testing

10h / Beginner to Intermediate

The link above includes a discount coupon, valid until 2026-08-31. After that it works as a regular course link.

Top comments (0)