Skip to content

Testing Standards & Policy

FieldValue
TypeSkill Resource
Source~/.copilot/skills/quality/references/testing-standards.md
DescriptionNot specified

Source Content

Testing Standards & Policy

This reference consolidates the testing layer selection, naming conventions, coverage policy, flake elimination, and the what-NOT-to-test list from the absorbed testing-standards skill.

The Testing Pyramid

Test the way the user uses the software, and write the cheapest test that gives that confidence. Push volume down the pyramid; reserve the slow, brittle layers for journeys nothing else can cover.

LayerToolTestsUse when
UnitVitestA pure function, reducer, hook, or Go function in isolationLogic has branches, edge cases, or math — and no DOM or network
ComponentReact Testing Library + userEventA component through its public behavior — render, interact, assertA user can see or do something; covers component + local state together
NetworkMSWComponent/integration behavior across a mocked network boundaryThe unit under test fetches or mutates; mock at the network edge, not the module
Accessibilityjest-axeNo WCAG violations in rendered outputEvery component test — expect(await axe(container)).toHaveNoViolations()
VisualLost PixelPixel diffs of stories/pages — never ChromaticA change could regress layout, spacing, or theme that assertions miss
E2EProject choice (no default)A full critical journey across real pagesA journey spans routes/auth/persistence; one happy path + one failure path

Test Naming and Structure

Naming Convention

Tests follow the pattern: it('does X when Y'). The name describes the behavior, not the implementation.

Good examples:

  • it('shows error message when credentials are invalid')
  • it('adds item to cart when button is clicked')
  • it('returns 0 discount for orders under $50')
  • it('navigates to dashboard after successful login')

Avoid:

  • it('works') — not specific
  • it('test 1') — not descriptive
  • it('should do stuff') — too vague
  • it('renders') — doesn’t describe behavior

Describe Block Organization

Organize describe blocks around units, then contexts or sub-behaviors:

describe('UserService', () => {
describe('createUser', () => {
describe('with valid input', () => {
it('creates user in database', () => {});
it('returns user with id', () => {});
});
describe('with invalid input', () => {
it('throws ValidationError for missing email', () => {});
it('throws ValidationError for invalid email format', () => {});
});
});
describe('deleteUser', () => {
it('removes user from database', () => {});
});
});

Arrange-Act-Assert (AAA) Structure

Every test follows the same three-phase structure:

  1. Arrange — set up test data and conditions
  2. Act — execute the code being tested
  3. Assert — verify the outcome

Use blank lines to separate the three phases:

it('calculates total with discount', () => {
// Arrange
const items = [
{ name: 'Widget', price: 100, quantity: 2 },
{ name: 'Gadget', price: 50, quantity: 1 },
];
const discountRate = 0.1;
// Act
const result = calculateTotal(items, discountRate);
// Assert
expect(result).toBe(225); // (200 + 50) * 0.9
});

Coverage Policy (the >=50% Line Floor)

The baseline is >=50% line coverage, enforced as one of ADR-005’s release gates — failing it blocks merge. Read the floor as a release minimum on business-critical code, not a target to game.

The Gate

  • It is a floor, not a goal. 50% of meaningful branches beats 90% padded with glue.
  • It measures lines, the cheapest signal — high line coverage with weak assertions is still weak.
  • Direct it at risk. Find uncovered branches in code that breaks badly; ignore coverage on generated, trivial, or glue code.
  • Coverage is one gate among several: typecheck, build, lint, test (>=50%), jest-axe, Lost Pixel, bundle budget, and the docs gate (ADR-005).

Anti-Gaming Rules

What NOT to do:

  • Don’t artificially raise coverage by testing trivial pass-throughs.
  • Don’t write assertions that don’t actually verify behavior.
  • Don’t count generated code (routeTree.gen.ts, codegen output).
  • Don’t test the framework or library (React, TanStack, Zod are already tested).
  • Don’t test private implementation details if only to hit a number.

Code Types and Coverage Targets

Code TypePriorityCoverage Target
Business logic (state management, API handlers, validation, payment)High80%+
UI components (but not trivial wrappers)Medium60-80%
Utility functions and transformersMedium60-80%
Custom hooksMedium60-80%
Static pages, simple wrappers, types/interfacesLower40-60%
Generated code, configuration filesSkip

Flake Elimination

A flaky test is a defect — quarantine and fix it, never paper over it with a blanket retry-til-green. The four most common causes and their fixes:

1. Timing / Async

Problem: Test doesn’t wait for async operations.

Fix: Use findBy* (waits automatically) or waitFor in assertions; always await userEvent calls.

// Flaky
it('shows user name', () => {
render(<UserProfile userId="123" />);
expect(screen.getByText('John')).toBeInTheDocument(); // May not exist yet
});
// Fixed
it('shows user name', async () => {
render(<UserProfile userId="123" />);
await expect(screen.findByText('John')).resolves.toBeInTheDocument();
});

2. Test-Order Dependency

Problem: Test state leaks from one test to the next; tests pass in one order, fail in another.

Fix: Fresh state for every test — each test sets up and tears down its own data. No shared mutable globals.

// Flaky
describe('Counter', () => {
const counter = new Counter(); // Shared!
it('increments', () => {
counter.increment();
expect(counter.value).toBe(1);
});
it('starts at zero', () => {
expect(counter.value).toBe(0); // Fails if this runs after the first test
});
});
// Fixed
describe('Counter', () => {
let counter: Counter;
beforeEach(() => {
counter = new Counter(); // Fresh instance
});
it('increments', () => {
counter.increment();
expect(counter.value).toBe(1);
});
it('starts at zero', () => {
expect(counter.value).toBe(0); // Passes
});
});

3. Real Time / Randomness

Problem: Test asserts against Date.now() or Math.random().

Fix: Fake the clock and seed randomness.

// Flaky
it('timestamps events', () => {
const event = createEvent();
expect(event.createdAt).toBe(Date.now()); // Fails: time moved
});
// Fixed
it('timestamps events', () => {
jest.useFakeTimers();
jest.setSystemTime(new Date('2024-01-15'));
const event = createEvent();
expect(event.createdAt).toEqual(new Date('2024-01-15'));
jest.useRealTimers();
});

4. Real Network

Problem: Test hits a live API endpoint.

Fix: MSW intercepts everything; an unmocked request is a failure, not a flake to retry.

// Flaky
it('fetches user', async () => {
// Hits the real API — network failure causes flake
const user = await fetch('/api/users/123').then(r => r.json());
expect(user.name).toBe('John');
});
// Fixed
it('fetches user', async () => {
// MSW intercepts and returns mock data
server.use(
rest.get('/api/users/:id', (req, res, ctx) =>
res(ctx.json({ id: '123', name: 'John' }))
)
);
const user = await fetch('/api/users/123').then(r => r.json());
expect(user.name).toBe('John');
});

What NOT to Test

Tests cost maintenance; some never repay it. Do not write tests for:

  • Implementation details — internal state, private methods, render counts, exact prop values.
  • The framework or the library — React, TanStack, Zod, or the standard library are already tested.
  • Generated coderouteTree.gen.ts, codegen output, snapshots of trivial markup.
  • Pure styling with no behavior — that is Lost Pixel’s job, not an assertion’s.
  • Trivial glue — one-line pass-throughs, barrel re-exports, constant maps.
  • Third-party network responses literally — mock the contract (MSW), do not hit the live API in CI.

House Rules

  1. Test behavior, not implementation. Query by role/label/text first (getByRole, getByLabelText); getByTestId is the last resort. Never assert on state, props, or internal calls.
  2. Mock at the edge. MSW for the network. Do not mock modules you own — real implementations beat fakes.
  3. Add a11y and visual checks where they belong. jest-axe in every component test; Lost Pixel on surfaces a diff would catch.
  4. Flaky tests are defects. Quarantine and fix them. Never retry-til-green, never test.retry().
  5. Scope tests to one behavior. One it() block per behavior; multiple assertions OK if they verify the same behavior.