webroad.online
  1. 1Web
  2. 2HTML
  3. 3CSS
  4. 4JavaScript
  5. 5TypeScript
  6. 6Git
  7. 7Tooling
  8. 8React
  9. 9State management
  10. 10Next.js
  11. 11Forms
  12. 12Data and backend
  13. 13SEO
  14. 14Tailwind CSS
  15. 15Animations
  16. 16Testing
  17. 17Architecture
Testing · Lesson 2 of 4

Vitest: unit tests

describe/test/expect, the right matchers, mocks and fake timers.

Updated

What Vitest is

Vitest is a test runner: it finds the test files (*.test.ts), runs them and reports what passed and what didn't. It's fast, understands TypeScript and ESM without configuration and has a Jest-compatible API (the best-known runner).

npm i -D vitest
npx vitest          # watch mode — reruns on every save
npx vitest run      # a single run (CI)

The structure

// src/shared/lib/price.test.ts
import { describe, expect, test } from 'vitest'
import { applyDiscount } from './price'

describe('applyDiscount', () => {
  test('WELCOME10 takes off 10%', () => {
    expect(applyDiscount(100, 'WELCOME10')).toBe(90)
  })

  test("an unknown code doesn't change the price", () => {
    expect(applyDiscount(100, 'NOPE')).toBe(100)
  })
})
  • describe groups related tests.
  • test (or it) — one case.
  • expect(value).matcher(expected) — the check.

Put the test next to the file under test (colocation) — easy to find, it moves along with it.

Matchers — kinds and when

Matcher Compares When
toBe(x) identity (Object.is) primitives: numbers, strings, booleans
toEqual(x) content, recursively objects and arrays
toStrictEqual(x) content + undefineds + class when exactness matters
toBeTruthy() / toBeNull() / toBeUndefined() special values —
toContain(x) an element in an array / a substring lists, texts
toHaveLength(n) the length lists
toThrow(msg?) the function throws validation
resolves / rejects a Promise async code
expect({ a: 1 }).toBe({ a: 1 })      // ✗ different objects
expect({ a: 1 }).toEqual({ a: 1 })   // ✓ the same content
expect(() => parseAge(-1)).toThrow('Invalid age')
await expect(loadUser(1)).resolves.toEqual({ id: 1 })

Mocks

A mock is a fake function that replaces a dependency (an API, time, randomness) so the test is fast and deterministic.

import { vi } from 'vitest'

const onSave = vi.fn()                       // a spy function
render(<Form onSave={onSave} />)
// ... the user submits the form
expect(onSave).toHaveBeenCalledWith({ email: 'a@x.com' })

vi.useFakeTimers()                           // you control time
vi.advanceTimersByTime(300)                  // e.g. for a debounce
Kind What it does
vi.fn() a function that records its calls
vi.spyOn(obj, 'method') spies on an existing method
vi.mock('./api') replaces a whole module
vi.useFakeTimers() controlled time

Don't overdo mocks: the more you mock, the less of reality you test. For HTTP requests, MSW intercepts at the network level — the code stays untouched.

Setting it up with Next.js

Next has an official guide for Vitest (with @vitejs/plugin-react and jsdom for components). Careful: async Server Components can't be unit-tested well yet — use E2E for them.

Summary

  • Vitest runs *.test.ts; describe / test / expect.
  • toBe for primitives, toEqual for objects.
  • vi.fn and fake timers for dependencies; mock as little as possible.

Official sources

Exercises

Was this page helpful?

One tap — no account needed.