Skip to content
End To End Tester

Vitest

A Vite-native test runner with a Jest-compatible API — faster startup, native ESM and TypeScript, and the cases where it is the better default.

2 min read · updated 19 September 2026

Vitest runs tests through Vite's transform pipeline. If a project already builds with Vite, its test runner now shares that configuration — the same aliases, the same plugins, the same TypeScript and JSX handling, with no second toolchain to keep in sync.

That is the whole pitch, and for a Vite project it is a strong one.

#What it looks like

Deliberately familiar:

typescript
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { applyDiscount } from './pricing';

describe('applyDiscount', () => {
  it.each([
    [12_000, 2_400],
    [10_000, 2_000],
    [9_999, 0]
  ])('discounts %i by %i', (subtotal, expected) => {
    expect(applyDiscount({ subtotalCents: subtotal }, policy).discountCents).toBe(expected);
  });
});

vi replaces jest as the mocking namespace and the methods line up: vi.fn, vi.spyOn, vi.mock, vi.useFakeTimers. Most Jest test bodies port by changing an import.

#Configuration

typescript
// vite.config.ts — one config for build and test
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import { resolve } from 'node:path';

export default defineConfig({
  plugins: [react()],
  resolve: { alias: { '@': resolve(__dirname, './src') } },   // shared with the build
  test: {
    globals: true,                 // describe/it/expect without importing
    environment: 'node',
    setupFiles: ['./vitest.setup.ts'],
    coverage: { provider: 'v8', reporter: ['text-summary', 'lcov'] },

    // The equivalent of Jest projects: DOM only where it is needed.
    workspace: [
      { test: { name: 'unit', environment: 'node', include: ['src/lib/**/*.test.ts'] } },
      { test: { name: 'dom', environment: 'jsdom', include: ['src/components/**/*.test.tsx'] } }
    ]
  }
});

The alias is defined once and used by both the application build and the tests. In a Jest setup the same information lives in moduleNameMapper and drifts.

#Where it is genuinely better

Startup. No separate transform step, and modules are transformed on demand. On a large suite this is the difference between a two-second and a twelve-second cold start, which matters for watch-mode discipline.

Native ESM. Jest's ESM support has improved and is still the most common source of configuration pain in a modern JavaScript project. Vitest was built for it.

Watch mode. Vite's module graph means only genuinely affected tests rerun. It is noticeably sharper than Jest's heuristic.

In-source testing. Tests can live in the module they test, stripped from the production build by define:

typescript
export function slugify(text: string) {
  return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
}

// Removed from the production bundle by `define: { 'import.meta.vitest': 'undefined' }`
if (import.meta.vitest) {
  const { it, expect } = import.meta.vitest;
  it('slugifies', () => expect(slugify('Hello, World!')).toBe('hello-world'));
}

Charming for small pure utilities; do not build a whole suite this way.

Browser mode. Runs the same tests in a real browser through Playwright rather than jsdom, which removes an entire class of "works in jsdom, breaks in Chrome" surprises for component tests.

#Where Jest still wins

  • Ecosystem. More matchers, more reporters, more integrations, and vastly more written about it when something goes wrong at 5pm.
  • Non-Vite projects. Adding Vite purely to get Vitest is a real cost.
  • Node-heavy backends. Jest's Node support is more battle-tested for server code that never touches a bundler.
  • React Native and some monorepo tooling still assume Jest.

#Migrating

bash
npm i -D vitest @vitest/coverage-v8
diff
- import { jest } from '@jest/globals';
+ import { vi } from 'vitest';
- jest.mock('./rates');
+ vi.mock('./rates');
- jest.useFakeTimers();
+ vi.useFakeTimers();

Then move moduleNameMapper to resolve.alias, testEnvironment to test.environment, and projects to test.workspace. For a suite of a few hundred files this is usually an afternoon. The parts that resist are anything depending on Jest internals, custom transforms, or timer semantics at the edges.

The honest summary: if you are on Vite, use Vitest. If you are not, the migration is real work for a speed gain you may be able to get more cheaply by replacing ts-jest with @swc/jest.

Common questions

Is Vitest a drop-in replacement for Jest?
Close, not exact. The assertion and mocking APIs are deliberately compatible, so most test bodies port unchanged. What differs is configuration, some timer behaviour, and anything relying on Jest internals or Babel transforms.
When should I choose Vitest over Jest?
When the project already uses Vite, when it is ESM-only, or when Jest's transform cost is dominating your suite time. Outside those cases Jest's larger ecosystem is worth more than the startup difference.
Does Vitest do browser testing?
It has a browser mode that runs tests in a real browser via Playwright or WebDriver, which is a genuine alternative to jsdom for component tests. It is not a replacement for an end-to-end tool.

Runnable samples for this page

last test results ↗

Working tests, not fragments — they run in CI on every push to 8exgh/endtoendtester-samples.

Was this page useful?