QuestionsTesting

Test suite performance and parallelization

Suite PerformanceHardTesting

A test suite takes 20 minutes and blocks every PR. How do you make it fast without losing confidence?

What it tests

Diagnosing what makes a suite slow and applying parallelization, sharding, and selection without sacrificing coverage.

Approach & answer

A slow suite is a real cost — it slows every merge, so people batch changes, skip running it locally, and lose the fast feedback tests exist to provide. Attack it in layers. First, DIAGNOSE: find the slow tests (runners report per-test/-file timing) and the slow LAYER. Usually the shape is wrong — too many slow e2e/integration tests doing work that unit tests could do (the ice-cream cone). Rebalancing toward the pyramid (push logic coverage down to fast unit tests, keep e2e to critical journeys) is the biggest structural win. Second, PARALLELIZE: unit/component runners (Jest, Vitest) already run test FILES across worker processes/threads on multiple cores — ensure that's on and workers are tuned to the machine; the prerequisite is ISOLATION (no shared state/ports/db rows), because parallelism exposes any hidden coupling as flakiness. Third, SHARD across MACHINES in CI: split the suite into N shards run on N runners in parallel (jest --shard=1/4, Playwright's built-in sharding), cutting wall-clock roughly linearly; combine with per-worker resource namespacing (own DB schema, own tmp dir, own port) so shards don't collide. Fourth, TEST SELECTION: run only what changed — Jest's --onlyChanged / --changedSince, or tooling that maps changed files to affected tests (Nx/Turbo affected graphs), so a one-file PR doesn't run 10k tests; keep the full suite for main/merge. Fifth, cut per-test OVERHEAD: avoid real network (MSW instead of live calls), fake timers instead of real waits, seed DB via fast fixtures/transactions with rollback instead of full migrations per test, reuse expensive setup with beforeAll where safe, and mock heavy modules you don't need. Sixth, CACHE: dependency and build caches, and transform caches (SWC/esbuild transforms are far faster than babel-ts) so startup isn't dominated by compilation. The guardrail throughout: speed must not come from deleting assertions or over-mocking until tests prove nothing — the goal is the same confidence, faster. Measure before and after, and watch that parallelization didn't introduce flakiness (the tax for hidden shared state).

Use this technique when

CI is slow; scaling a growing suite; tuning parallel workers and sharding.

Code

Lever                    How                                   Watch out for
-----------------------  ------------------------------------  --------------------------
rebalance the pyramid    move coverage from e2e -> unit        keep e2e for critical flows
parallelize (1 machine)  Jest/Vitest workers across cores      needs test isolation
shard (N machines)       jest --shard=1/4, pw sharding         per-worker db/port/tmp
test selection           --onlyChanged / affected graph        run full suite on main
cut per-test overhead    MSW, fake timers, fast fixtures       don't over-mock -> false green
caching                  deps + SWC/esbuild transform cache    invalidate correctly

Rule: same confidence, faster. Speed must NOT come from weaker assertions.
Measure before/after; parallelism turns hidden shared state into flakiness.

References