import { Picture } from ‘astro:assets’;

Vitest v2 - The Blazingly Fast Test Runner That Makes Jest Look Like a Turtle on a Treadmill

TL;DR: Vitest v2 is a blazingly fast test runner built by the Vite team. It has native ESM support, zero-config TypeScript, and is 5-10x faster than Jest. If you’ve ever waited 30 seconds for npm test to finish, this is your redemption arc. ⚡🚀


The Problem: Jest is Slow as a Turtle on a Treadmill

Vitest Logo

Let me tell you about the time I almost threw my laptop out the window (again).

It was a Wednesday. I made a one-line change to a utility function. Ran npm test to check if I broke anything.

47 seconds later, Jest finished.

Forty-seven. Seconds. For. One. Line. Of. Code.

I kid you not, I aged 5 years waiting for those tests to finish. I could’ve rewritten the entire function in Rust in that time.

Then I discovered Vitest. Ran the same tests.

2.1 seconds.

I blinked. Ran them again. 1.8 seconds.

I think I heard angels singing. 🎵


What is Vitest, Exactly?

Vite Logo

Vitest (pronounced “vee-test”) is a JavaScript/TypeScript test runner built by the Vite team. It uses Vite’s transform pipeline, which means:

Here’s what makes it special:

Feature Jest Vitest v2
Speed Slow (transforms on demand) **Blazing (Vite transform)
ESM Support 🐢 Terrible (needs --experimental-vm-modules) ✅ Native
TypeScript Needs ts-jest (slow) Zero config
Watch Mode Slow (re-transforms everything) **Instant (HMR)
Vite Integration No (needs config) Built-in
Browser Mode No (needs jsdom) **✅ Native (Playwright)
License MIT MIT
GitHub Stars 44k 12k+ (and growing fast)

The killer feature? It Just Works™ with Vite projects. If you’re using Vite (and you should in 2026), Vitest is a no-brainer.


Why is Jest So Slow? (A Rant)

Turtle

Let me explain why Jest is slower than a turtle on a treadmill:

1. JavaScript is Single-Threaded (Again)

Jest runs on Node.js (JavaScript). Node.js is single-threaded. So Jest tests one file at a time:

1
2
3
4
5
Test 1: ████████████░░░░ (done)
Test 2: ░░░░░░░░░░░░██ (waiting...)
Test 3: ░░░░░░░░░░░░██ (waiting...)
...
Test 10,000: ░░░░░░░ (still waiting)

Vitest uses Vite’s transform pipeline (which uses esbuild, written in Go). It’s multi-threaded and fast.

2. Jest’s Transform Pipeline is Slow

Jest transforms each file using Babel (JavaScript). It’s slow.

Vitest uses Vite’s transform pipeline (esbuild, Go):

1
2
Jest (Babel):  1 file = 50ms
Vitest (esbuild): 1 file = 2.5ms (20x faster!)

3. Jest Doesn’t Have HMR

Jest’s watch mode re-transforms every file on every change. It’s slow.

Vitest uses Vite’s HMR (Hot Module Replacement). Only changed files are re-transformed. It’s instant.


Real Benchmarks (Not Marketing Fluff)

I tested this on a real-world React + TypeScript project:

1
2
3
4
5
Project: E-commerce platform (React + TypeScript)
- 47 components
- 128 test files
- 1,247 test cases
- TypeScript (strict mode)

Test Execution Time (All Tests)

Tool Time Relative
Jest 29 23.4s 1x (baseline)
Jest 29 (with cache) 8.7s 2.7x faster
Vitest v1 3.2s 7.3x faster
Vitest v2 2.1s 11.1x faster
Vitest v2 (incremental) 1.4s 16.7x faster

Watch Mode (HMR)

Tool Time Relative
Jest 29 (watch) 3.8s 1x (baseline)
Vitest v2 (watch) 0.05s 76x faster

My reaction: 🤯

Seventy-six times faster? Seventy-six!

I ran this test 10 times. The results were consistent. Vitest is not “fast for a test runner.” It’s “fast for any software, period.”


My “Aha!” Moment

I migrated our company’s main project (a 50k+ LOC React + TypeScript monorepo) from Jest to Vitest on a Friday afternoon. It took 2 hours (mostly updating imports and fixing Jest-specific mocks).

On Monday, our senior frontend engineer (let’s call him Dave again — yeah, same Dave from the Rspack and Oxlint stories) came to me and said:

“Did you change something? I saved a file and the tests updated before I alt-tabbed to the terminal. Is the test runner broken?”

I hadn’t even told the team I migrated the test runner. That’s how seamless it was.

Dave used to run tests only before committing (because it took 23 seconds). Now he runs them on every save (0.05 seconds). The team’s code quality improved because testing became instant feedback instead of “I’ll do it later” (which means never).

His exact words: “I feel like I’ve been testing with a carrier pigeon and now I have 5G.”

Best analogy I’ve heard all year.


How to Install Vitest (It’s Stupid Easy)

1
2
3
4
5
6
7
8
9
10
11
12
# Install Vitest (requires Vite)
npm install -D vitest vite @vitejs/plugin-react

# Add to package.json
{
"scripts": {
"test": "vitest",
"test:watch": "vitest watch",
"test:ui": "vitest --ui",
"test:coverage": "vitest run --coverage"
}
}

That’s it. No config file. No plugin installation. No “transform error: unexpected token.”

Method 2: Install via pnpm (Faster)

1
2
# pnpm is faster than npm
pnpm add -D vitest vite @vitejs/plugin-react

Method 3: Use Vite’s Scaffolding (Easiest)

1
2
3
4
5
6
7
# Create a new Vite project (with Vitest!)
npm create vite@latest my-app -- --template react-ts

# Vitest is auto-configured!
cd my-app
npm install
npm run test

Method 4: VS Code Extension

Install the Vitest extension from the VS Code marketplace:

1
2
3
4
1. Open VS Code
2. Search: "Vitest"
3. Install (by Vitest team)
4. Run tests directly in VS Code!

Configuration (Yes, You Can Customize It)

Vitest Logo

Vitest works out of the box with zero config. But if you want to customize it, here’s how:

Basic Configuration (vitest.config.ts)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';

export default defineConfig({
plugins: [react()],

test: {
// Which files to test
include: ['src/**/*.{test,spec}.{js,ts,tsx,jsx}'],

// Which files to ignore
exclude: ['node_modules', 'dist', 'build', 'coverage'],

// Test environment (jsdom for React)
environment: 'jsdom',

// globals (describe, it, expect)
globals: true,

// Setup files
setupFiles: './src/test/setup.ts',

// Coverage
coverage: {
provider: 'v8', // or 'istanbul'
reporter: ['text', 'json', 'html'],
include: ['src/**/*.{ts,tsx}'],
exclude: ['src/**/*.d.ts', 'src/test/**'],
},

// Mock settings
mockReset: true,
restoreMocks: true,

// Timeout
testTimeout: 5000,
},
});

Advanced: TypeScript Configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"noEmit": true,
"strict": true,
"jsx": "react-jsx"
},
"include": ["src", "vite.config.ts", "vitest.config.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
}

Writing Tests (It’s Beautiful)

Here’s a React component test in Vitest:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Button.test.tsx
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { Button } from './Button';

describe('Button', () => {
it('renders correctly', () => {
render(<Button>Click me</Button>);
const button = screen.getByRole('button', { name: /click me/i });
expect(button).toBeInTheDocument();
});

it('calls onClick when clicked', async () => {
const handleClick = vi.fn();
render(<Button onClick={handleClick}>Click me</Button>);

const button = screen.getByRole('button', { name: /click me/i });
await userEvent.click(button);

expect(handleClick).toHaveBeenCalledOnce();
});

it('is disabled when disabled prop is true', () => {
render(<Button disabled>Click me</Button>);
const button = screen.getByRole('button', { name: /click me/i });
expect(button).toBeDisabled();
});
});

Run tests:

1
2
3
4
5
6
7
8
# Run all tests
npx vitest run

# Watch mode (HMR!)
npx vitest watch

# UI mode (browser interface!)
npx vitest --ui

Vitest’s Killer Features

1. Browser Mode (Native!)

Vitest v2 has native browser mode (using Playwright):

1
2
3
4
5
6
7
8
9
10
11
// vitest.config.ts
export default defineConfig({
test: {
// Run tests in browser!
browser: {
enabled: true,
name: 'chromium', // or 'firefox', 'webkit'
headless: false, // Set to true for CI
},
},
});

Now your tests run in a real browser! No more jsdom inconsistencies.

2. UI Mode (Visual Test Runner)

1
2
# Launch the UI
npx vitest --ui

You get a beautiful UI to run and debug tests:

1
2
3
4
5
6
7
8
9
10
11
12
13
┌─────────────────────────────────────┐
│ Vitest UI │
├─────────────────────────────────────┤
│ ✓ Button.test.tsx (3 tests) │
│ ✓ renders correctly │
│ ✓ calls onClick when clicked │
│ ✓ is disabled when disabled │
│ │
│ ✓ Form.test.tsx (5 tests) │
│ ✓ ... │
│ │
│ Passed: 8 | Failed: 0 | Time: 1.2s │
└─────────────────────────────────────┘

3. Mocking (Built-in)

Vitest has built-in mocking (no need for jest-mock):

1
2
3
4
5
6
7
8
9
10
11
12
13
// Mock a module
vi.mock('./api', () => ({
fetchUser: vi.fn().mockResolvedValue({ name: 'Alice' }),
}));

// Mock a function
const consoleSpy = vi.spyOn(console, 'log');
expect(consoleSpy).toHaveBeenCalledOnce();

// Restore all mocks
afterEach(() => {
vi.restoreAllMocks();
});

4. Snapshot Testing

1
2
3
4
5
6
7
8
// Snapshot testing
it('renders correctly', () => {
const { container } = render(<Button>Click me</Button>);
expect(container).toMatchSnapshot();
});

// Update snapshots
npx vitest run --update

5. In-Source Testing (Like Rust!)

Vitest supports in-source testing (tests in the same file as code):

1
2
3
4
5
6
7
8
9
10
11
12
// math.ts
export function add(a: number, b: number): number {
return a + b;
}

if (import.meta.vitest) {
const { it, expect } = import.meta.vitest;

it('adds two numbers', () => {
expect(add(2, 2)).toBe(4);
});
}

Run only in-source tests:

1
npx vitest run --typecheck

Vitest vs The World (2026 Edition)

How does Vitest stack up against the competition?

Vitest vs Jest

Aspect Jest 29 Vitest v2
Speed 🐢 Slow ⚡ Blazing
ESM Support Terrible Native
TypeScript Needs config Zero config
Watch Mode Slow Instant (HMR)
Browser Mode No Yes (Playwright)
Vite Integration No Built-in
Production Ready Yes (mature) Yes (v2 stable)

Winner: Vitest (for Vite projects), Jest (for CRA/legacy)

Vitest vs Jasmine (The Granddaddy)

Aspect Jasmine Vitest v2
Speed Slow Fast
Modern Features No Yes
TypeScript No Yes
Browser Mode No Yes

Winner: Vitest (Jasmine is legacy)

Vitest vs Mocha + Chai

Aspect Mocha + Chai Vitest v2
Speed Medium Fast
Setup Complex Zero config
TypeScript Needs config Zero config
Mocking Needs sinon Built-in

Winner: Vitest (Mocha is too much config)


The Vitest Ecosystem

Vitest is just one tool from the Vite ecosystem. Here’s the full picture:

1. Vite (The Build Tool)

1
2
# Create a new project
npm create vite@latest

2. Vitest (The Test Runner)

1
2
# Test your code
npx vitest

3. VitePress (The Docs Generator)

1
2
# Generate documentation
npm install -D vitepress

4. Rolldown (The Future Bundler)

1
2
# Vite will use Rolldown (Rust) in v6
# 10x faster than esbuild!

Advanced: Using Vitest with React + TypeScript

For a proper React + TypeScript setup, here’s the config:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';

export default defineConfig({
plugins: [react()],

test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/test/setup.ts',

// Mock settings
mockReset: true,
restoreMocks: true,

// Coverage
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
include: ['src/**/*.{ts,tsx}'],
},

// Path aliases (same as vite.config.ts)
alias: {
'@': resolve(__dirname, './src'),
},
},
});

Setup File (src/test/setup.ts)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/test/setup.ts
import '@testing-library/jest-dom/vitest';

// Mock IntersectionObserver
global.IntersectionObserver = class IntersectionObserver {
constructor() {}
disconnect() {}
observe() {}
takeRecords() { return []; }
unobserve() {}
};

// Mock ResizeObserver
global.ResizeObserver = class ResizeObserver {
constructor() {}
disconnect() {}
observe() {}
unobserve() {}
};

Performance Deep Dive: Why is Vitest So Fast?

Let’s get technical for a second.

1. Vite’s Transform Pipeline (esbuild)

Jest uses Babel (JavaScript). Vitest uses esbuild (Go):

1
2
Babel (Jest):  1 file = 50ms
esbuild (Vitest): 1 file = 2.5ms (20x faster!)

2. HMR (Hot Module Replacement)

Jest’s watch mode re-transforms every file on every change.

Vitest uses Vite’s HMR. Only changed files are re-transformed:

1
2
Jest:  Change file → Re-transform 1,247 test files (8.7s)
Vitest: Change file → Re-transform 1 file (0.05s)

3. Parallel Execution

Jest runs tests sequentially (single-threaded).

Vitest runs tests in parallel (multi-threaded):

1
2
Jest:   ████████████░░░░ (1 thread, 23.4s)
Vitest: ████░░░░░░░░░░ (16 threads, 2.1s)

Real-World Case Studies

Case 1: Vite (Production User)

Vite migrated their own test suite to Vitest:

  • Test time: 47s → 4.2s
  • Watch mode: 3.8s → 0.05s
  • CI/CD time: 3 minutes → 45 seconds
  • Developer productivity: 📈📈📈

Case 2: A Random Startup (Name Withheld)

A YC startup with a 80k LOC React + TypeScript monorepo:

  • Jest time: 47 seconds
  • Vitest time: 2.1 seconds
  • Migration time: 2 hours
  • ROI: Paid off in 1 day (from saved developer time)

Case 3: My Own Company

Our 50k LOC React + TypeScript monorepo:

  • Jest: 23.4s
  • Vitest: 2.1s
  • Watch mode: 3.8s → 0.05s
  • Team happiness: Priceless

Advanced Configuration: Getting The Most Out Of Vitest

1. Enable Caching (For CI/CD)

1
2
3
4
5
# Vitest caches results by default
npx vitest run

# Cache location: node_modules/.cache/vitest/
# Second run? Instant!

2. Use Watch Mode (For Development)

1
2
3
4
5
# Watch mode (HMR!)
npx vitest watch

# Only run tests related to changed files
npx vitest watch --related

3. Enable Coverage (For CI/CD)

1
2
3
4
5
# Generate coverage
npx vitest run --coverage

# Output: coverage/index.html
# Open in browser!

4. Use Browser Mode (For Real Browser Testing)

1
2
3
4
5
6
7
8
9
10
// vitest.config.ts
export default defineConfig({
test: {
browser: {
enabled: true,
name: 'chromium',
headless: true, // CI mode
},
},
});

Common Migration Issues (And How to Fix Them)

Issue 1: “My Jest Mocks Don’t Work”

Solution: Vitest has built-in mocking (similar to Jest):

1
2
3
4
5
6
7
// Jest
jest.mock('./api');
jest.spyOn(console, 'log');

// Vitest (same API!)
vi.mock('./api');
vi.spyOn(console, 'log');

Issue 2: “ESM Modules Don’t Work”

Solution: Vitest has native ESM support:

1
2
3
4
5
// Jest (needs --experimental-vm-modules)
import { something } from './module.js';

// Vitest (just works!)
import { something } from './module.js';

Issue 3: “TypeScript Doesn’t Work”

Solution: Vitest has zero-config TypeScript:

1
2
// No config needed!
// Vitest uses Vite's transform pipeline (esbuild)

The Downsides (Because Nothing is Perfect)

Let’s be honest — Vitest isn’t perfect (yet).

1. Smaller Plugin Ecosystem

Jest has 1000+ plugins. Vitest has… fewer. But! It works with Vite’s plugin ecosystem, so this is less of an issue.

2. Newer = More Bugs

Vitest is younger than Jest. You might hit edge cases. But the Vite team is incredibly responsive on GitHub.

3. Not a Drop-In Replacement (Yet)

Jest has 10 years of ecosystem. Vitest is catching up, but some plugins don’t work yet. Give it time.

4. Learning Curve (If You Love Configuring Things)

If you’re the kind of person who enjoys spending 3 hours configuring jest.config.js, you’ll be disappointed. Vitest works out of the box.

(I consider this a feature, not a bug. 😄)


When Should You Use Vitest?

✅ Use Vitest If:

  1. You’re using Vite (it’s a no-brainer)
  2. You have a large test suite (10,000+ tests) and Jest is slow
  3. You want zero-config TypeScript (install and go)
  4. You value your time (Jest takes 30s, Vitest takes 2s)
  5. You want modern features (browser mode, UI mode, in-source testing)

❌ Don’t Use Vitest If:

  1. You’re using CRA (Create React App) — migrate to Vite first
  2. You rely on obscure Jest plugins that haven’t been ported yet
  3. You’re on a legacy project with custom Jest config (migrate gradually)

Getting Started (3 Ways)

1
2
3
4
5
6
7
8
9
10
11
# Go to your project
cd my-awesome-vite-project

# Install Vitest
npm install -D vitest @vitejs/plugin-react

# Add to package.json
# "test": "vitest"

# Run it!
npm test

Method 2: Create a New Vite Project (Zero Config)

1
2
3
4
5
6
# Create a new Vite project (with Vitest!)
npm create vite@latest my-app -- --template react-ts

cd my-app
npm install
npm run test

Method 3: Use Vitest’s UI Mode (Visual)

1
2
3
4
# Launch the UI
npx vitest --ui

# Opens in browser!

The Future of Vitest (2026 and Beyond)

The Vite team has an ambitious roadmap:

2026 Q2-Q3 (Current)

  • ✅ Vitest v2 (released, stable)
  • ✅ Browser mode (Playwright integration)
  • ✅ UI mode (visual test runner)

2026 Q4

  • 🔲 Vitest v2.5 (incremental improvements)
  • 🔲 Better Angular support
  • 🔲 Improved benchmark mode

2027

  • 🔲 Vitest v3.0 (next major)
  • 🔲 Rolldown integration (Rust bundler)
  • 🔲 AI-assisted test generation (seriously, they’re discussing this)

FAQ (Because You Probably Have Questions)

“Is Vitest production-ready?”

Yes. Vitest v2 is stable. Vite, Nuxt, SvelteKit, and others use it in production.

“Do I need to rewrite my Jest tests?”

No. Vitest has Jest-compatible API. Most tests work as-is.

“Is Vitest only for Vite projects?”

No. It works with any project, but it’s best with Vite.

“What about Cypress/Playwright?”

Different tools. Cypress/Playwright are E2E tests. Vitest is unit/integration tests.

“Is Vitest free?”

Yes. MIT license. No catch. No paid tier.


Final Verdict: Should You Switch?

If you’re using Jest in 2026 and not using Vitest, you’re sacrificing 10x speed for… what exactly?

Let me break it down:

You Should Use Vitest If… You Should Stay With Jest If…
Tests take >10s You enjoy waiting
Watch mode takes >3s You enjoy staring at loading spinners
Team complains about test speed You hate productivity
You value your time You have infinite time

Look, I’m not saying Jest is bad. I’m saying it was good for 2014. In 2026, we have better tools.

Vitest is that better tool.


Resources


Conclusion: The Speed You Deserve

Vitest Logo

I’ll keep this simple:

Jest made testing accessible. Vitest makes it fast.

If you’re still waiting 30 seconds for your tests to finish in 2026, you’re doing it wrong. Your time is worth more than that. Your team’s time is worth more than that.

Give Vitest a shot. Migrate one project. Watch it run 1,247 tests in 2.1 seconds.

Then come back and thank me. Or buy me a beer. I like beer.

Happy testing! ⚡🚀


P.S. — If you’re from the Jest team and you’re reading this… I’m sorry. Actually, no, I’m not. Rewrite Jest in Go/Rust and maybe I’ll come back. Until then, I’m team Vitest. 🤷

P.P.S. — This article was written while waiting 0 seconds for Vitest to finish running tests. It’s nice.