Skip to content
C Codeloom
Testing

Vitest Tutorial

A practical guide to Vitest, the testing framework built for modern JavaScript projects. Setup, syntax, mocking, coverage, and how it differs from Jest in ways that matter.

·4 min read · By Codeloom
Beginner 9 min read

What you'll learn

  • What Vitest is and why it exists
  • How to write and run your first tests
  • How mocking and spies work
  • How to measure coverage
  • Migration notes from Jest

Prerequisites

  • Basic JavaScript or TypeScript

Vitest is a testing framework that feels like Jest but runs through Vite. It is fast, ESM-native, and integrates cleanly with TypeScript without the configuration drama. This tutorial gets you productive in an afternoon.

What and Why

Vitest is a unit test runner designed to share its toolchain with Vite. That means the same module resolution, the same plugins, the same transforms. Tests run through the same pipeline as your application, so you stop maintaining two parallel configurations.

The reason it caught on is partly speed and partly ergonomics. Hot reload of tests during development is genuinely useful. Native support for TypeScript, JSX, and ESM means less setup. The API is intentionally close to Jest, so migrating from an existing test suite is mostly cosmetic.

Mental Model

Picture Vitest as a thin layer over Vite. When you run a test, Vite transforms the file, Vitest runs it in a worker, and the results stream back to a watcher. The runner uses workers in parallel so independent files do not block each other. State between tests in different files is isolated unless you explicitly share it.

If you have used Jest, the keywords are familiar: describe, it, expect, beforeEach. The difference is that the transform pipeline is yours, not the runner’s, so weird build issues mostly disappear.

Hands-on Example

Install and write a first test.

npm install -D vitest
// math.js
export const add = (a, b) => a + b;

// math.test.js
import { describe, it, expect, vi } from 'vitest';
import { add } from './math.js';

describe('add', () => {
  it('sums two numbers', () => {
    expect(add(2, 3)).toBe(5);
  });
});

describe('mocking', () => {
  it('replaces a dependency', () => {
    const logger = { info: vi.fn() };
    logger.info('hello');
    expect(logger.info).toHaveBeenCalledWith('hello');
  });
});

Run with npx vitest. The watcher reruns only the affected files when you save.

test file change
 |
 v
[Vite transform] -> TS / JSX / ESM
 |
 v
[worker pool] -> isolated module graph per file
 |
 v
[reporter] -> watch UI / terminal
              |
              v
      coverage + summary
How Vitest runs your tests through Vite

Add coverage with vitest --coverage after installing @vitest/coverage-v8.

Common Pitfalls

The first pitfall is mixing Jest globals and Vitest globals in the same project. They look identical but come from different packages. Pick one and remove the other from your dependencies.

The second is forgetting that ESM is the default. Modules are not magically reset between files the way they were in Jest’s CommonJS world. Use vi.resetModules() when you actually need a clean module graph.

The third is over-mocking. Vitest makes it easy to stub everything, and a test suite that stubs too much stops verifying real behaviour. Mock the boundary you do not own and run the rest for real.

Practical Tips

Run Vitest in UI mode with vitest --ui during development. The web interface shows the dependency graph and lets you click into individual tests, which is faster than scrolling terminal output.

Co-locate tests next to the source they cover. The convention foo.ts plus foo.test.ts keeps related code together and makes deletions cleaner.

Treat snapshot tests as a backup, not a default. They catch broad regressions but rot quickly. Prefer assertions on specific properties.

Wrap-up

Vitest is what testing in a Vite-based project should feel like: fast, native, and out of your way. If you are starting a new project today there is little reason to reach for an older runner.