Unit Testing Checklist

Steps a software engineer follows when writing and running unit tests for a new module or significant refactor — from scoping the unit under test through coverage verification and PR sign-off.

6 sections 24 steps Collects data
1

Scope and Setup

  1. Identify the unit under test
    • Pin down the smallest meaningful unit — a pure function, a class method, a React component. If you find yourself needing to spin up a database or hit a real HTTP endpoint, you've crossed into integration territory and should split that into a separate suite.

  2. Link the ticket and acceptance criteria
    Collects url
  3. Confirm the test framework and runner
    • Match the repo's existing convention — Jest or Vitest for TypeScript, pytest for Python, RSpec for Ruby, JUnit for Java. Don't introduce a second framework into a monorepo without team buy-in; the maintenance cost shows up six months later.

  4. Choose mocking strategy for collaborators
    • Decide upfront which dependencies are stubbed, mocked, or replaced with fakes. Network calls, the system clock, randomness, and the filesystem are the usual suspects. Over-mocking produces brittle tests that pass even when the unit is broken — only mock at module boundaries you actually own.

2

Test Case Design

  1. Enumerate happy-path cases
    • Cover the canonical inputs from the spec — the cases a product manager would describe in a demo. Use AAA structure (Arrange / Act / Assert) and keep each test focused on one behavior so a failure name tells you what broke.

  2. Enumerate edge and boundary cases
    • Empty inputs, null/undefined, zero, negative numbers, off-by-one boundaries, max-length strings, Unicode, leap-day dates, timezone edges. The bugs you'll ship are almost always in this list.

  3. Enumerate error and failure cases
    • Assert on the specific exception type and message — not just "throws". Include cases where collaborators reject (network timeout, DB constraint violation, malformed JSON) so the unit's error handling is exercised, not just the happy path.

  4. Decide whether property-based tests apply
    • For parsers, serializers, and pure transformations, fast-check (JS) or Hypothesis (Python) catches edge cases example-based tests miss. Skip for UI components and side-effect-heavy code.

    Collects list
  5. Add a property-based test suite
    • Pin the seed for reproducibility and cap the run count so CI doesn't balloon. Write the property as an invariant the unit must always satisfy (round-trip, idempotence, ordering preservation), not as a restated example.

3

Implementation

  1. Write tests against the public API
    • Test exported functions and class methods, not private internals. Tests bound to private implementation details break on every refactor and lose their value as a safety net.

  2. Use deterministic fixtures and clocks
    • Freeze time with sinon fake timers, jest.useFakeTimers(), or freezegun. Seed random number generators. Avoid new Date() and Math.random() inside assertions — these are the leading cause of flaky tests.

  3. Name tests by behavior, not by method
    • Prefer "returns 401 when the token is expired" over "test_validate()". The CI failure summary should read like a spec; an engineer who didn't write the test should know what broke without opening the file.

4

Local Execution and Coverage

  1. Run the suite locally and confirm green
  2. Run the suite three times to catch flakes
    • Order-dependent tests, leaked global state, and timing-sensitive assertions only show up on repeat. jest --runInBand followed by parallel runs catches both shared-state bugs and concurrency bugs.

  3. Capture coverage report for the unit
    Collects number Collects number Collects file
  4. Confirm coverage meets the team threshold
    • Most teams set 80% line / 70% branch as the floor for new code. Coverage is a smoke detector, not a fire alarm — a 95% covered unit with weak assertions is worse than 80% covered with sharp ones.

    Collects list
  5. Add tests to close the coverage gap
    • Inspect the coverage report's uncovered branches — usually error handlers and defensive guards. If a branch genuinely can't be reached, delete it rather than add a contrived test to satisfy the metric.

5

Static Analysis and Quality Gates

  1. Run the linter and formatter
    • ESLint + Prettier, ruff + black, RuboCop — whichever the repo uses. Pre-commit hooks via husky or lefthook prevent lint errors from ever reaching CI.

  2. Run the type checker on test files
    • Type-check tests with the same strictness as production (tsc --noEmit, mypy). Loose any in tests masks the real type contract; if your mock satisfies any, the test isn't proving the unit accepts the real shape.

  3. Review SonarQube or Code Climate findings
    • Address new code smells, duplicated blocks, and cyclomatic complexity warnings introduced by this change. Existing debt isn't your problem to fix here — but don't add to it.

6

PR and Sign-Off

  1. Open the PR with test summary in the description
    • List what's covered, what's intentionally not (and why), and the coverage delta. Reviewers shouldn't have to reconstruct your test strategy from the diff alone.

  2. Confirm CI runs all required checks green
    • Branch protection should require unit tests, lint, type check, and coverage to pass before merge. If a check is flaky, fix it or quarantine it — never get into the habit of re-running until green.

  3. Request review from CODEOWNERS
  4. Record final sign-off
    Collects list Collects paragraph

Use this template

Copy it to your account, customize the steps, and run it with your team in minutes.


Sections 6
Steps 24
Category Software Development
Price Free to start
Need a different process

Browse hundreds of free templates across every team and industry.

Back to template library

Run Unit Testing Checklist with your team

Customize the steps, assign roles, set a schedule, and keep a complete record for every run.