C.W.K.
Stream
Lesson 01 of 04 · published

Install, Configure, First Green Test

~18 min · vitest-setup, install, config

Level 0Test Curious
0 XP0/32 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"Five minutes from npm install to the first green check."

Install in One Line

Vitest is one dependency for the runner, plus whatever DOM emulator and Testing Library helpers you want layered on top. For a typical React + TypeScript project, the install command is one line.

The --save-dev flag (or -D) puts everything under devDependencies — these packages never ship to your production bundle, and they shouldn't.

The Minimal Config

If your project already has vite.config.ts, Vitest reads its test field directly — you might not need a separate config file at all. If you do want one (test-specific environment, aliases that differ from build), drop a vitest.config.ts alongside.

Two fields earn their keep on day one:

  • environment: 'happy-dom' for component tests, 'node' for pure logic tests. Set per-file with // @vitest-environment node at the top if you mix.
  • globals: true exposes describe, it, expect globally without import. Convenience vs explicitness — pick the team default and stick with it. Code below uses globals off and imports explicitly because that's what reads better in code review.
Add a test script to package.json. Long-term, you'll type npm test a thousand more times than npx vitest run. Set it up once: "test": "vitest", "test:run": "vitest run", "test:ui": "vitest --ui". The CLI's two-character commands compound.

The First Test File

Convention: put test files next to the code they test (format.ts + format.test.ts) or in a parallel __tests__/ folder. The next-to convention is easier to navigate and easier to delete cleanly when the code goes away. The __tests__/ convention isolates better for monorepos.

Vitest discovers files matching *.{test,spec}.{ts,tsx,js,jsx} by default — no manifest, no opt-in registration. Drop a file, save, and the watcher picks it up in the next tick.

setupFiles — the One Hook You'll Reach For

When you need code to run before every test file — extending matchers, polyfilling, configuring testing-library — that's setupFiles. Most React projects use one: a single vitest.setup.ts that imports @testing-library/jest-dom/vitest to expose matchers like toBeInTheDocument() and toHaveClass().

The config gets small over time, not big. First-day Vitest config has environment, setupFiles, and maybe globals. If your config grows past 30 lines, ask why — usually you've grown organic plugins that another tool already does, or you've enabled features (coverage, UI mode) at the wrong layer.

Code

Install — one line, devDependencies·bash
# Vitest core + a fast DOM emulator + React Testing Library + matchers
npm install -D vitest happy-dom \
  @testing-library/react @testing-library/user-event @testing-library/jest-dom

# First sanity check — should print 'Vitest 4.x.x'
npx vitest --version
Minimal Vitest config — happy-dom + setup file·typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'happy-dom',
    globals: false,                       // import describe/it/expect explicitly
    setupFiles: ['./vitest.setup.ts'],    // run before every test file
    css: true,                            // process CSS imports without crashing
    include: ['src/**/*.{test,spec}.{ts,tsx}'],
  },
});
vitest.setup.ts — testing-library matchers·typescript
// vitest.setup.ts
import '@testing-library/jest-dom/vitest';

// Add any global mocks or polyfills here, sparingly.
// Example — silence noisy 'not implemented' warnings from happy-dom:
// vi.spyOn(console, 'warn').mockImplementation(() => {});
First test file — run with `npm test`·typescript
// src/lib/format.test.ts
import { describe, it, expect } from 'vitest';
import { formatCurrency } from './format';

describe('formatCurrency', () => {
  it('formats whole numbers with a currency symbol', () => {
    expect(formatCurrency(1234, 'USD')).toBe('$1,234.00');
  });

  it('rounds to two decimals', () => {
    expect(formatCurrency(1.005, 'USD')).toBe('$1.01');
  });
});

External links

Exercise

In a project of yours, run the install command, drop a vitest.config.ts, and write the smallest possible test — expect(1 + 1).toBe(2). Run npm test and watch it pass. Then change it to expect(1 + 1).toBe(3) and watch what failure looks like in your terminal. Note the colors, the diff, the file pointer. That failure UX is what you'll live in.
Hint
If npm test hangs without printing anything, you're likely in watch mode (the default for vitest). Press q to quit, or use vitest run for a single non-watching pass — useful in CI.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.