Use this skill when testing REST or GraphQL APIs, implementing contract tests, setting up mock servers, or validating API behavior. Triggers on API testing, Postman, contract testing, Pact, mock servers, MSW, HTTP assertions, response validation, and any task requiring API test automation.
Use this skill when testing REST or GraphQL APIs, implementing contract tests, setting up mock servers, or validating API behavior. Triggers on API testing, Postman, contract testing, Pact, mock servers, MSW, HTTP assertions, response validation, and any task requiring API test automation.
When this skill is activated, always start your first response with the 🧢 emoji.
API Testing
A comprehensive framework for testing REST and GraphQL APIs with confidence.
Covers the full spectrum from unit-level handler tests to cross-service contract
tests, with emphasis on what to test at each layer and why - not just syntax.
Designed for engineers who can write tests but need opinionated guidance on
strategy, tooling, and avoiding common traps.
When to use this skill
Trigger this skill when the user:
Writes tests for a REST or GraphQL API endpoint
Sets up integration or end-to-end tests for an HTTP service
Implements contract testing between a consumer and provider
Creates mock servers or stubs for downstream dependencies
Validates response schemas or payload shapes
Tests authentication flows (JWT, OAuth, API keys)
Tests error handling, edge cases, or failure scenarios
Asks about Supertest, Pact, MSW, Zod validation, or Apollo testing
Do NOT trigger this skill for:
UI/component testing concerns (use a frontend-testing skill instead)
Load/performance testing - that is a separate discipline with different tooling
Key principles
Test behavior, not implementation - Assert on what the API returns to
callers, not on how internal functions are wired together. An endpoint test
that reaches the router and asserts on status code + response body is worth
ten unit tests on internal helpers.
Isolate at the right boundary - Unit tests mock everything below the
handler. Integration tests use a real database (test container or in-memory).
Contract tests verify only the interface promise. Choose the boundary that
catches the most bugs with the least brittleness.
Schema-first assertions - Validate response shape with a schema (Zod,
JSON Schema) rather than field-by-field assertions. One schema assertion
catches structural regressions that 20 individual assertions would miss.
Contracts are promises, not snapshots - A contract test verifies that a
provider will always satisfy what a consumer expects. It must be run on every
deploy. A snapshot that drifts silently is worse than no test.
Mock at the network boundary, not inside functions - Use MSW or nock to
intercept HTTP calls at the network layer. Mocking individual imported
functions couples tests to implementation details and breaks on refactors.
Core concepts
API test types
Type
What it tests
Scope
Speed
Unit
Handler logic, middleware, validators
Single function
Fast
Integration
Full request cycle with real DB
Service in isolation
Medium
Contract
Interface promise between consumer + provider
Two services
Medium
End-to-end
Complete user journey across services
Full stack
Slow
Default strategy: Integration tests for business logic (they give the most
confidence per line of test code). Unit tests for pure transformation logic.
Contract tests at service boundaries. E2E only for the critical happy path.
Mock vs stub vs fake
Term
Definition
Use for
Mock
Records calls and verifies expectations
Verifying side effects (emails sent, events published)
Stub
Returns canned responses without recording
Replacing slow/expensive dependencies
Fake
Working implementation of a lighter version
In-memory DB, in-process message queue
Prefer fakes over stubs over mocks. Mocks that verify call counts are fragile
and break whenever you refactor internal wiring.
Schema validation
Validate response schemas at the integration test level. Use Zod because it:
Produces TypeScript types from the same definition (no duplication)
Gives precise error messages when assertions fail
Can be shared between test and production code for dual validation
Common tasks
Test REST endpoints with Supertest
Supertest binds directly to an Express/Fastify app without starting a real
HTTP server. Use it for integration tests that exercise the full request pipeline.
// tests/users.test.tsimport request from'supertest';
import { app } from'../src/app';
import { db } from'../src/db';
beforeEach(async () => {
await db.migrate.latest();
await db.seed.run();
});
afterEach(async () => {
await db.migrate.rollback();
});
describe('GET /users/:id', () => {
it('returns 200 with user data for a valid id', async () => {
const res = awaitrequest(app)
.get('/users/1')
.set('Authorization', 'Bearer test-token')
.expect(200);
expect(res.body).toMatchObject({
id: 1,
email: expect.stringContaining('@'),
createdAt: expect.any(String),
});
});
it('returns 404 when user does not exist', async () => {
const res = awaitrequest(app)
.get('/users/99999')
.set('Authorization', 'Bearer test-token')
.expect(404);
expect(res.body).toMatchObject({
type: expect.stringContaining('not-found'),
status: 404,
});
});
it('returns 401 when no auth token is provided', async () => {
awaitrequest(app).get('/users/1').expect(401);
});
});
Test GraphQL APIs with Apollo Server Testing
Use @apollo/server test utilities to execute operations in-process. This
avoids the overhead of HTTP while still exercising the full resolver chain.
For detailed Pact consumer and provider verification examples, see references/contract-and-auth-testing.md.
Mock APIs with MSW
MSW intercepts at the Service Worker level in browsers and at the network
layer in Node.js. Use it to replace real API calls in tests without patching
imports.
Define schemas once and use them in both production code and tests. A failed
schema parse gives a precise error pointing to exactly which field is wrong.
Error paths are where bugs live in production; clients rely on error contracts too
Cover 401, 403, 404, 409, 422, 500 for every resource
Mocking the module under test
Circular: if you mock the handler, you're not testing the handler
Mock dependencies (DB, HTTP calls), not the code being tested
Sharing state between tests
One test leaks data into the next; flaky tests that fail in suites but pass alone
Seed and tear down in beforeEach/afterEach; use transactions that roll back
Contract tests that are just snapshots
Snapshots catch no semantic regressions; they auto-update and drift silently
Use Pact with structured matchers; run provider verification in CI
Testing internal implementation details
Tests break on refactors even when behavior is unchanged; slows iteration
Test via the public HTTP interface; verify outputs, not internal calls
Ignoring response headers
Security and cache headers are part of the contract; clients depend on them
Assert Content-Type, Cache-Control, X-Request-Id, and auth headers
Gotchas
Shared test database state causes flaky tests - Tests that don't clean up after themselves leave rows that cause unique constraint failures or wrong counts in subsequent tests. The tests pass in isolation but fail in suites. Use database transactions that roll back after each test, or seed and truncate in beforeEach/afterEach.
MSW onUnhandledRequest: 'warn' silently passes unmocked calls - With the default warn setting, any request not matched by a handler goes through to the real network. In CI this causes non-deterministic test behavior. Set onUnhandledRequest: 'error' so unmatched requests fail loudly.
Supertest doesn't start a real server but shares app state - Supertest binds to the app instance. If the app has module-level singletons (connection pools, caches), those persist across tests. Make sure database and cache connections are properly reset between test runs, or tests will interfere with each other.
Pact consumer tests passing doesn't mean provider will pass - Consumer Pact tests only verify that the mock returns the expected shape. The provider verification step (running against the real provider) is where real contract drift is caught. Both steps must run in CI; running only the consumer half gives false confidence.
Schema validation with .toMatchObject() misses extra fields - Jest's toMatchObject does a partial match: extra fields on the response body pass silently. If the API starts leaking sensitive fields (passwords, internal IDs), these tests won't catch it. Use Zod's strict() mode or exact schema validation for security-sensitive fields.
References
For detailed patterns on specific tools and setups, read the relevant file from
the references/ folder:
references/msw-patterns.md - MSW setup for Node.js and browser environments,
handler patterns, and recipes for common scenarios
references/contract-and-auth-testing.md - Pact consumer/provider contract testing and authentication flow test examples
Only load a references file when the current task requires it - they are
detailed and will consume context.
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: