DEV Community

MSakai
MSakai

Posted on

Five things that break when you move from Jest to Vitest

Vitest advertises a Jest-compatible API, and it delivers. describe, test, expect, toHaveBeenCalledWith — all identical. Which is why the five things that aren't compatible are so disorienting: the suite mostly runs, and then a handful of files fail for reasons the error messages don't explain.

1. Globals are opt-in

Jest injects describe and expect into the global scope. Vitest doesn't, by default.

// vitest.config.ts
export default defineConfig({
  test: { globals: true },
})
Enter fullscreen mode Exit fullscreen mode

Plus, for types:

// tsconfig.json
{ "compilerOptions": { "types": ["vitest/globals"] } }
Enter fullscreen mode Exit fullscreen mode

The alternative — importing explicitly — is the better long-term choice, but do it as a separate commit. Mixing "migrate the runner" with "touch every test file" makes the diff unreviewable.

import { describe, test, expect, vi } from 'vitest'
Enter fullscreen mode Exit fullscreen mode

2. moduleNameMapper doesn't exist

Path aliases move out of the test config and into Vite's resolver, where they're shared with your build:

// jest.config.js
moduleNameMapper: { '^@/(.*)$': '<rootDir>/src/$1' }
Enter fullscreen mode Exit fullscreen mode
// vitest.config.ts
import { fileURLToPath } from 'node:url'

export default defineConfig({
  resolve: {
    alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) },
  },
})
Enter fullscreen mode Exit fullscreen mode

This is a net win — one place instead of two — but it means your test aliases now affect your bundle. Check them.

3. jest.mock hoists differently than you remember

Both hoist. The difference is what happens to variables the factory closes over. In Vitest you need vi.hoisted:

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

Jest's jest.mock tolerated a variable named with a mock prefix as a special case. Vitest has no such carve-out, so the mockFoo naming convention that used to be load-bearing in your Jest suite now does nothing.

4. restoreMocks behaves differently than clearMocks

Three settings, easily confused, and the Jest defaults don't carry over:

Option Effect
clearMocks Resets calls and instances between tests
mockReset The above, plus resets the implementation to undefined
restoreMocks The above, plus restores the original implementation for spies

If you relied on Jest's restoreMocks: true in jest.config.js, set it explicitly:

export default defineConfig({
  test: { restoreMocks: true },
})
Enter fullscreen mode Exit fullscreen mode

Symptom when you forget: a spy created with vi.spyOn in one test is still installed in the next one, and a completely unrelated test starts asserting against a stub.

5. Environments are per-file, and jsdom is not bundled

Vitest ships no DOM implementation. Install one:

npm i -D jsdom
Enter fullscreen mode Exit fullscreen mode

Then set it globally, or per file — which is the feature Jest never had:

// @vitest-environment jsdom
Enter fullscreen mode Exit fullscreen mode

That comment at the top of a file overrides the config for that file alone. Mixing node-environment and jsdom tests in one run stops being a config negotiation.

What to do first

Run the suite before changing a single test file. The failures cluster: aliases first, then mocks, then environment. Fixing them in that order means each batch of failures has one cause, rather than three tangled together.

The parts that don't break — assertions, matchers, snapshots, coverage output — are genuinely the bulk of a suite. The migration is real, but it's an afternoon, not a rewrite.


These posts come out of material I build for my Udemy courses — 25 of them now, mostly drill-based, across Go, Python, TypeScript, testing and Three.js. If this was useful, the full list is at udemy-c1f90.web.app. The links on that page carry a coupon I refresh each month, which usually lands around half the list price.

Top comments (0)