Q33 of 37 · API testing
How would you measure and improve API test execution speed?
Short answer
Short answer: Measure first: per-test runtime, parallelism utilisation, network time vs setup time. Optimise: parallel runs, persistent connections, shared auth tokens, scoped fixtures (per-suite not per-test), and removing tests that aren't pulling weight. Aim for full suite under 5 minutes; smoke under 60s.
Detail
An API test suite that takes 30 minutes is a suite that gets bypassed. Speed is a quality metric.
Step 1 — Measure. Per-test runtime, sorted descending, gives you the optimisation list. Most frameworks expose this:
pytest --durations=20
mocha --reporter min
mvn surefire:test -DforkCount=4
Capture also:
- CPU / parallelism utilisation (htop during the run).
- Time spent in network vs setup vs teardown vs assertions.
- Test count over time (is the suite growing faster than it's optimising?).
Step 2 — High-leverage optimisations, ranked:
1. Parallelise. Most API test suites are network-bound — perfect for parallel runs. Set workers to N cores and verify tests are isolated (see test-isolation question).
2. Persistent HTTP connections. Reuse keep-alive sockets:
const ctx = await request.newContext({ baseURL }); // shared per worker
Eliminates TLS handshake per call.
3. Cached auth tokens. The biggest single win for tests with auth. Cache per worker:
let token: string;
beforeAll(async () => { token = await getToken(); });
A 1000-test suite that re-auths each test wastes ~10s per test on the auth round-trip.
4. Scoped fixtures. Don't createUser per test if the user is read-only — create per describe. Save 80% of setup time without sacrificing isolation.
5. Shared base data. Read-only fixtures (a static demo organisation) created once at suite startup, deleted at end. Many tests read; few mutate. Mutate-tests get their own fixture.
6. Remove dead weight. The brutal one. Tests that:
- Test things already covered by lower-layer tests.
- Test deprecated features.
- Have always passed (no signal in 6 months). Cut them. Speed is also a hygiene metric.
7. Ship work to the API. Instead of 10 GETs to verify state, ask the API for a single batched response. Reduces N+1 in tests too.
8. Skip costly setup conditionally. If a test only needs an existing user, reuse one across tests in the same describe.
9. Mock external services in unit/component tests, hit the real services only in a small E2E layer. The biggest single source of test slowness is hitting real third parties.
Anti-optimisations:
- Reducing assertions to "make tests pass faster." You're not optimising; you're losing coverage.
- Cutting timeouts aggressively — flake skyrockets.
- Pre-seeding "the test database" globally — gains speed, loses isolation. Already covered as anti-pattern in test-isolation.
Targets:
- Smoke suite: < 60 seconds. Runs on every PR.
- Full regression: < 5 minutes. Runs on merge to main.
- E2E full + UI: < 15 minutes. Runs nightly.
These are aspirational; if you're at 30 minutes, halve to 15 in a quarter, then halve again. Compound returns.
The senior signal: measurement-first, parallelism + caching as the high-leverage moves, and the discipline to delete dead tests rather than carry them.