Use this skill when deciding what to test, choosing between test types, designing a testing strategy, or balancing test coverage. Triggers on test pyramid, unit vs integration vs e2e, contract testing, test coverage strategy, TDD, BDD, testing ROI, and any task requiring testing architecture decisions.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Use this skill when deciding what to test, choosing between test types, designing a testing strategy, or balancing test coverage. Triggers on test pyramid, unit vs integration vs e2e, contract testing, test coverage strategy, TDD, BDD, testing ROI, and any task requiring testing architecture decisions.
When this skill is activated, always start your first response with the 🧢 emoji.
Test Strategy
A testing strategy answers three questions: what to test, at what level, and how
much. Without a strategy, teams end up with either too many slow, brittle e2e
tests or too few tests overall - both are expensive. This skill gives the
judgment to design a test suite that provides high confidence, fast feedback, and
low maintenance cost.
When to use this skill
Trigger this skill when the user:
Asks which type of test to write for a given scenario
Wants to design a testing strategy for a new service or feature
Needs to decide between unit, integration, and e2e tests
Asks about test coverage targets or metrics
Wants to implement contract testing between services
Is dealing with flaky tests and needs a remediation plan
Asks about TDD or BDD workflow
Do NOT trigger this skill for:
Writing the actual test code syntax for a specific framework (defer to framework docs)
Performance testing or load testing strategy (separate domain)
Key principles
Test behavior, not implementation - Tests should survive refactoring. If
moving logic between private methods breaks your tests, the tests are testing
the wrong thing. Test public contracts and observable outcomes.
The Testing Trophy over the pyramid - The classic pyramid (many unit,
fewer integration, few e2e) was coined before modern tooling. The Trophy
(Kent C. Dodds) weights integration tests most heavily: static analysis at
the base, unit tests for isolated logic, integration tests for the bulk of
coverage, and a few e2e tests for critical paths.
Fast feedback loops - A test suite that takes 30 minutes to run is a
test suite that doesn't get run. Design for speed: unit tests in
milliseconds, integration tests in seconds, e2e tests reserved for CI only.
Test at the right level - The cost of a test rises as you move up the
stack (slower, more brittle, harder to debug). Test each concern at the
lowest level that meaningfully exercises it.
Flaky tests are worse than no tests - A test that sometimes fails trains
the team to ignore failures. A flaky test in CI delays every deploy. Fix or
delete flaky tests immediately; never tolerate them.
Core concepts
Test types taxonomy
Type
What it tests
Speed
Cost
Use for
Static
Type errors, lint violations
Instant
Near-zero
Type safety, obvious mistakes
Unit
Single function/class in isolation
< 10ms
Low
Pure logic, edge cases, algorithms
Integration
Multiple modules together with real dependencies
100ms-2s
Medium
Service layer, DB queries, API handlers
E2E
Full user journey through deployed stack
5-60s
High
Critical user paths, smoke tests
Contract
API contract between producer and consumer
Seconds
Medium
Microservice boundaries
The Testing Trophy
/\
/e2e\ - Few: critical flows only
/------\
/ integ \ - Most: service + DB + API
/------------\
/ unit \ - Some: pure logic and edge cases
/----------------\
/ static \ - Always: types, lint, format
/--------------------\
The key insight is that integration tests give the best ROI for most application
code: they test real behavior through real dependencies without the brittleness
of e2e tests.
Test doubles
Use the minimum isolation necessary for the test's purpose:
Double
When to use
Risk
Stub
Replace slow/unavailable dependency, return canned data
Low - no behavior coupling
Mock
Verify a side effect was triggered (email sent, event published)
Medium - couples to call signature
Spy
Observe calls without replacing behavior
Medium - couples to call count/args
Fake
Replace infrastructure with working in-memory version
Low - tests real behavior patterns
Prefer fakes for infrastructure (in-memory DB, in-memory queue). Mocks should
be reserved for side effects you cannot otherwise observe.
Coverage metrics
Metric
What it measures
When to use
Line coverage
% of lines executed
Baseline floor, not a target
Branch coverage
% of conditional paths taken
Better for logic-heavy code
Mutation coverage
% of introduced bugs caught by tests
Gold standard for test quality
Line coverage above ~80% has diminishing returns and creates perverse incentives.
Mutation coverage reveals whether tests actually assert meaningful things.
Common tasks
Choose the right test type - decision matrix
When deciding what level to test something at, apply this logic:
Is this pure logic with no external dependencies?
YES → Unit test
NO → Does it require a real DB / HTTP call / file system?
YES → Integration test (use real infrastructure or a fast fake)
NO → Does it span multiple services or require a browser?
YES → E2E test (sparingly)
NO → Integration test
Additional rules:
Cross-service API boundaries → Contract test (Pact or similar)
Complex UI interaction that cannot be tested at component level → E2E
Algorithm with many edge cases → Unit test per edge case + one integration
Design a test suite for a new service
Structure the test suite before writing the first line of code:
Map the test surface - Identify all external I/O: databases, queues,
HTTP clients, file system. These are the integration seams.
Choose infrastructure strategy - Real DB with test containers, in-memory
fake, or Docker Compose. Prefer real DBs for schema-heavy services.
Define the testing trophy for your context - Decide the ratio before
you write tests. A typical distribution: 60% integration, 30% unit, 10% e2e.
Set up test data factories - Centralize how test objects are created.
Factories prevent fragile fixtures and make tests self-documenting.
Wire CI from day one - Tests that only run locally drift. Run unit +
integration in every PR, e2e in pre-merge or nightly.
Write effective unit tests - patterns
Unit tests work best for:
Pure functions (same input always gives same output)
Complex conditional logic with many branches
Data transformations and parsing
Domain model invariants
Arrange-Act-Assert structure:
test('applies 10% discount for orders over $100', () => {
// Arrangeconst order = buildOrder({ subtotal: 120 });
// Actconst discounted = applyLoyaltyDiscount(order);
// Assertexpect(discounted.total).toBe(108);
});
Parameterize boundary conditions:
test.each([
[99, 0], // just below threshold - no discount
[100, 10], // exactly at threshold
[200, 20], // above threshold
])('order of $%i gets $%i discount', (subtotal, expectedDiscount) => {
const order = buildOrder({ subtotal });
expect(applyLoyaltyDiscount(order).discount).toBe(expectedDiscount);
});
See references/test-patterns.md for more patterns.
Write integration tests - database and API
For database integration tests:
// Use real DB, roll back after each testbeforeEach(() => db.beginTransaction());
afterEach(() => db.rollbackTransaction());
test('saves user and returns with id', async () => {
const user = await userRepo.create({ name: 'Alice', email: 'alice@test.com' });
expect(user.id).toBeDefined();
const found = await userRepo.findById(user.id);
expect(found.name).toBe('Alice');
});
For HTTP API integration tests, test the full request cycle:
Fix the root cause - If time-dependent: freeze time with a clock fake.
If shared state: isolate in beforeEach/afterEach. If async: await properly.
Un-quarantine and monitor - After the fix, restore to main suite and
watch for a week of clean runs before declaring victory.
Anti-patterns
Anti-pattern
Problem
What to do instead
Testing the framework
expect(orm.save).toHaveBeenCalled() tests that the ORM is wired, not that data was saved
Assert the actual state after the operation
Snapshot testing everything
Snapshot tests fail on any UI change, creating noise and review fatigue
Use snapshots only for serialized output you rarely change (e.g., generated JSON schema)
100% coverage target
Creates tests that execute code without asserting anything meaningful
Set mutation score targets instead; aim for critical-path coverage
Giant test setup
Hundreds of lines of arrange code obscures what's actually being tested
Use builder/factory patterns; set only the fields that matter to the specific test
Mocking what you don't own
Mocking third-party libraries breaks on upgrades and doesn't test actual integration
Write a thin adapter you own, then mock your adapter
Skipping the testing pyramid for greenfield
Starting with e2e tests "because they test everything" leads to slow, brittle suites
Build bottom-up: unit tests first, integration second, e2e last
Gotchas
Testing implementation details breaks on every refactor - Tests that assert internal function calls or private state are coupled to the how, not the what. When you move logic between files or rename functions, these tests fail even though nothing broke. Test through the public API and observable outputs only.
Transaction rollback in DB tests does not catch commit-time failures - Rolling back a transaction after each test is fast but skips any constraints or triggers that only fire on COMMIT (e.g., deferred foreign key checks in PostgreSQL). For critical paths, run at least a subset of tests against a real transaction that commits.
Mutation testing tools report false positives on unreachable branches - Some generated mutants will be in dead code paths that are never exercised. A "survived mutant" in code guarded by a feature flag or error path that is structurally unreachable is not a test gap. Review mutation reports in context.
Quarantined flaky tests accumulate and are never fixed - Moving tests to a flaky folder or CI job without a SLA for fixing them creates a graveyard. Set a 2-week SLA: fix or delete. A quarantine suite that grows signals a systemic problem, not isolated test issues.
100% line coverage does not mean the test suite is valuable - You can achieve 100% coverage with tests that assert nothing meaningful (just call the function). Mutation score is the real quality signal. If your 100% coverage suite has a 40% mutation score, 60% of your tests are noise.
References
For detailed content on specific topics, read the relevant file from references/:
references/test-patterns.md - Common testing patterns: builders, fakes, parameterized tests, and when to use each
Only load a references file if the current task requires deep detail on that topic.
Companion check
On first activation of this skill in a conversation: check which companion skills are installed by running ls ~/.claude/skills/ ~/.agent/skills/ ~/.agents/skills/ .claude/skills/ .agent/skills/ .agents/skills/ 2>/dev/null. Compare the results against the recommended_skills field in this file's frontmatter. For any that are missing, mention them once and offer to install: