Vitest

48Vitest (modern unit testing)

What you will master here

  • Why Vitest vs Jest
  • Test structure: describe / it / expect
  • Mocks, spies, fake timers
  • Coverage with v8 / istanbul
  • Snapshot testing
  • React component testing with Vitest + Testing Library
  • Watch mode + parallel + UI

48.1 Why Vitest

npm i -D vitest @vitest/coverage-v8 @vitest/ui

48.2 First test

// src/sum.test.ts
import { describe, it, expect } from 'vitest';
import { sum } from './sum';

describe('sum', () => {
  it('adds two numbers', () => {
    expect(sum(1, 2)).toBe(3);
  });
  it('handles negatives', () => {
    expect(sum(-1, -2)).toBe(-3);
  });
});
npx vitest                # watch mode by default
npx vitest run            # single run (CI)
npx vitest --ui           # interactive UI in browser
npx vitest --coverage     # with coverage

48.3 Expect API (Jest-compatible)

expect(value).toBe(x);                    // ===
expect(obj).toEqual({ a: 1 });            // deep equal
expect(obj).toMatchObject({ a: 1 });      // subset
expect(arr).toContain(x);
expect(arr).toHaveLength(3);
expect(fn).toThrow('boom');
expect(fn).toThrow(MyError);
expect(value).toBeDefined();
expect(value).toBeNull();
expect(value).toBeTruthy();
expect(value).toBeGreaterThan(5);
expect(value).toBeCloseTo(0.3, 5);        // float
expect(str).toMatch(/regex/);

// Async
await expect(promise).resolves.toBe(42);
await expect(promise).rejects.toThrow();

48.4 Mocks & spies

import { vi, expect, it } from 'vitest';

// Spy on an existing object
const logger = { info: (m: string) => m };
const spy = vi.spyOn(logger, 'info');
logger.info('hello');
expect(spy).toHaveBeenCalledWith('hello');

// Standalone mock function
const fn = vi.fn().mockReturnValue(42);
expect(fn()).toBe(42);
expect(fn).toHaveBeenCalledOnce();

// Mock a module
vi.mock('node:fs', () => ({
  readFileSync: vi.fn(() => 'mocked contents'),
}));

// Reset between tests
beforeEach(() => vi.clearAllMocks());

48.5 Fake timers

it('debounce fires once', () => {
  vi.useFakeTimers();
  const fn = vi.fn();
  const d = debounce(fn, 300);
  d(); d(); d();
  vi.advanceTimersByTime(299);
  expect(fn).not.toHaveBeenCalled();
  vi.advanceTimersByTime(1);
  expect(fn).toHaveBeenCalledOnce();
  vi.useRealTimers();
});

48.6 Snapshots

it('renders user card', () => {
  const html = render(<UserCard name="Alice" />).container.innerHTML;
  expect(html).toMatchSnapshot();
});

// Inline
expect(value).toMatchInlineSnapshot(`
  { "a": 1, "b": 2 }
`);

Run npx vitest -u to update snapshots when output changes intentionally.

48.7 React component testing

// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
export default defineConfig({
  plugins: [react()],
  test: { environment: 'jsdom', setupFiles: ['./test-setup.ts'] },
});

// Button.test.tsx
import { render, screen, userEvent } from '@testing-library/react';
import { Button } from './Button';

it('fires onClick', async () => {
  const onClick = vi.fn();
  render(<Button onClick={onClick}>Buy</Button>);
  await userEvent.click(screen.getByRole('button', { name: 'Buy' }));
  expect(onClick).toHaveBeenCalledOnce();
});

48.8 Vitest vs Jest

AspectVitestJest
ESMNativeWorkarounds needed
SpeedFaster (Vite)Slower
APIJest-compatibleOriginal
ConfigSame as Vitejest.config.js
EcosystemYounger but growingHuge

Module 57 — Vitest Q&A

Why pick Vitest over Jest?
ESM-native, integrated with Vite's transform pipeline (faster, no Babel), Jest-compatible API for easy migration. For Vite-based projects (React/Vue/Svelte modern setups), Vitest is the default choice now.
What's the difference between toBe and toEqual?
toBe: strict equality (===). toEqual: deep value equality on objects/arrays. toBe({a:1}) fails because different references; toEqual({a:1}) passes.
How do you test an async function?
Two ways: await expect(promise).resolves.toBe(x), or await the value first then expect normally. For rejection: .rejects.toThrow().
What is a snapshot test?
A test that compares the current output to a saved snapshot file. Useful for HTML/JSON output that's hard to assert field-by-field. Update with vitest -u when output changes intentionally.
How do you fake timers?
vi.useFakeTimers(). Then vi.advanceTimersByTime(N) moves time forward; setTimeout/setInterval fire at synthetic times. Restore real timers in afterEach.
How do you mock a module?
vi.mock('module', () => ({ exportName: vi.fn() })) at the top of the test file. Vitest hoists it before imports. Use vi.unmock or vi.doMock for finer control.
When to write a unit test vs E2E in Playwright?
Unit (Vitest): pure logic, transformations, utility functions — fast, isolated. E2E (Playwright): user journeys, integration, browser-specific behaviour. Most logic should be unit-tested; reserve E2E for what actually requires a browser.