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.
Scope and Setup
-
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.
-
Link the ticket and acceptance criteriaCollects url
-
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.
-
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.
Test Case Design
-
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.
-
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.
-
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.
-
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 -
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.
Implementation
-
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.
-
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.
-
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.
Local Execution and Coverage
-
Run the suite locally and confirm green
-
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.
-
Capture coverage report for the unitCollects number Collects number Collects file
-
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 -
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.
Static Analysis and Quality Gates
-
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.
-
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.
-
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.
PR and Sign-Off
-
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.
-
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.
-
Request review from CODEOWNERS
-
Record final sign-offCollects list Collects paragraph
Use this template
Copy it to your account, customize the steps, and run it with your team in minutes.
Browse hundreds of free templates across every team and industry.
Back to template libraryRun Unit Testing Checklist with your team
Customize the steps, assign roles, set a schedule, and keep a complete record for every run.