Intermediate
Quick reference for testing — Vitest/Jest APIs, matchers, mocking recipes, supertest and Playwright snippets, and TDD checklists.
Unit TestsVitestTDDPlaywright
Test Structure
Vitest / Jest Skeleton (AAA)
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
describe('feature', () => {
beforeEach(() => { /* fresh setup */ });
afterEach(() => { vi.restoreAllMocks(); });
it('does the thing', () => {
const input = makeInput(); // Arrange
const out = doThing(input); // Act
expect(out).toBe(expected); // Assert
});
});
Lifecycle Hooks
| Hook | Runs |
| beforeAll | Once before the whole block |
| beforeEach | Before every test |
| afterEach | After every test (cleanup) |
| afterAll | Once after the whole block |
Modifiers
it.only(...) // run just this test
it.skip(...) // skip
it.todo('name') // placeholder
it.each([[a, b]])('$a -> $b', (a, b) => { ... });
Matchers
| Matcher | Checks |
| toBe(x) | === / reference identity |
| toEqual(x) | Deep structural equality |
| toStrictEqual(x) | Deep + types + undefined keys |
| toBeTruthy / toBeNull | Truthiness / null |
| toContain(x) | Array member or substring |
| toHaveLength(n) | Array/string length |
| toMatch(/re/) | String against regex |
| toMatchObject(x) | Subset of object props |
| toThrow(err) | Function throws |
| toBeCloseTo(n) | Floating-point equality |
| .not / .resolves / .rejects | Negate / await a promise |
expect(x).not.toBe(y);
await expect(promise).resolves.toEqual(v);
await expect(promise).rejects.toThrow(/msg/);
expect(fn).toThrow(TypeError);
Mocking
Functions & Spies
const fn = vi.fn();
fn.mockReturnValue(42);
fn.mockResolvedValue({ ok: true }); // async
fn.mockImplementation((x) => x * 2);
vi.spyOn(obj, 'method').mockReturnValue(1);
expect(fn).toHaveBeenCalled();
expect(fn).toHaveBeenCalledTimes(2);
expect(fn).toHaveBeenCalledWith('arg');
expect(fn).toHaveReturnedWith(42);
Modules & Timers
vi.mock('./db', () => ({ query: vi.fn() }));
vi.clearAllMocks(); // reset call history
vi.restoreAllMocks(); // undo spies
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-01-01'));
vi.advanceTimersByTime(1000);
vi.useRealTimers();
Test Doubles
| Type | Role |
| Stub | Canned return values |
| Spy | Records real calls |
| Mock | Programmed + verified |
| Fake | Lightweight working impl |
Integration & E2E
Supertest (API)
import request from 'supertest';
import { app } from '../app';
const res = await request(app)
.post('/api/login')
.set('Authorization', 'Bearer <token>')
.send({ email, password });
expect(res.status).toBe(200);
expect(res.body).toHaveProperty('token');
expect(res.headers['content-type']).toMatch(/json/);
Playwright (E2E)
import { test, expect } from '@playwright/test';
test('login', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('a@b.com');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByRole('heading', { name: 'Dashboard' }))
.toBeVisible();
await expect(page).toHaveURL(/\/dashboard/);
});
Preferred Locators
| Locator | Use |
| getByRole | Buttons, headings, links (best) |
| getByLabel | Form fields |
| getByText | Non-interactive content |
| getByTestId | Last resort |
TDD, Snapshots & CLI
TDD Cycle
| Step | Do |
| Red | Write a failing test |
| Green | Simplest code to pass |
| Refactor | Clean up, stay green |
Snapshots
expect(node).toMatchSnapshot();
expect(slug).toMatchInlineSnapshot(`"hello-world"`);
// update intentionally: vitest -u
CLI
vitest # watch mode
vitest run # single run (CI)
vitest run --coverage
vitest -t "login" # filter by name
vitest related src/x.ts # tests touching a file
playwright test --ui # Playwright debug UI
Coverage Metrics
| Metric | Meaning |
| Statements | % of statements run |
| Branches | Each if/else side hit (best signal) |
| Functions | % of functions called |
| Lines | % of lines executed |