Skip to content

Testing Strategy — Theory

Testing Strategy — Theory (interview deep-dive)

Section titled “Testing Strategy — Theory (interview deep-dive)”

A test that asserts internal calls (e.g., “spy was called 3 times”) breaks every refactor — even when behavior is unchanged. Prefer black-box assertions on inputs/outputs and visible side effects.

Symptoms of over-mocked tests:

  • Test code longer than impl.
  • Refactor breaks tests despite identical external behavior.
  • Tests still pass when impl is bypassed/no-op (mocks satisfied).

Mock at true boundaries:

  • External APIs (network, third-party).
  • Time, randomness.
  • Email, SMS, payment provider.

Real wherever cheap:

  • In-memory or containerized DB (PG, Mongo).
  • Redis (real or ioredis-mock).
  • Local file system (temp dir).

Real DB tests catch SQL bugs, migration drift, index issues that mocks never will.

Microservices integration via Pact (or similar):

  1. Consumer writes a test asserting the calls it makes and what it expects back. Pact records this as a contract file.
  2. Provider runs the contract against its own service in CI, verifying it satisfies expectations.
  3. Broker (Pact Broker, Hive) stores/distributes contracts.

Catches breakage at provider side before deploy, without running both ends. Replaces brittle E2E for service-to-service correctness.

Common causes:

  • Time-dependent (Date.now(), sleeps). Fix: inject clock.
  • Network calls without retries.
  • Parallel tests sharing DB / state.
  • Order dependence (test A leaves data for test B).
  • Random values without fixed seed.
  • Animations / timing in UI tests.

Policy: zero tolerance. Fix or quarantine in 1 day.

  • Factories > fixtures. Build minimal valid object per test.
  • Database transaction rollback per test — fast, isolates, no migrations between.
  • Truncate/seed — when transaction won’t work (e.g., tests use commit).
  • Realistic data: anonymized prod sample. Beware PII.

Tools mutate source (+-, ><=, drop branch). If tests still pass, the mutant “survived” — your tests didn’t really cover it.

Score = killed / total. ~70% is good. Slow to run; nightly only.

Specify invariants; framework generates inputs.

// fast-check
fc.assert(fc.property(fc.array(fc.integer()), arr => {
const sorted = sort(arr);
return sorted.length === arr.length && isSorted(sorted);
}));

Finds edge cases you’d miss. Pair with example tests.

  • Load — sustained expected traffic.
  • Stress — beyond expected, find breakpoint.
  • Spike — sudden burst.
  • Soak — long-duration, find leaks.

Always measure percentiles. Beware coordinated omission (tools that wait for response under saturation underreport tail latency). Use wrk2, k6 constant-arrival-rate.

  1. Pyramid vs trophy — when does each fit? Pyramid suits monolith with rich domain logic. Trophy suits microservices where wiring/integration is most of the bug surface.
  2. How do you keep integration tests fast? Containers shared across test files; transaction rollback; parallelism with isolated schemas; pin DB version.
  3. What’s flaky test policy? Block PRs from quarantine bin; fix or delete in N days.
  4. Why might 100% coverage still be insufficient? Tests can be coverage without assertions; mutation testing / boundary cases not exercised.
  5. How to test code that calls a third-party API? Use wiremock / msw / a contract test against a sandbox. Don’t hit prod.
  6. What is Pact, and where is it useful? Consumer-driven contract — verify provider without joint deploy.
  7. TDD — when do you not use it? Spikes / exploration; throwaway code; UI prototyping. Use after design clarifies.
  8. How to handle non-deterministic features in tests? Inject clock, RNG, IDs; freeze time for snapshot.
  9. How to test event-driven system? Verify side effects (DB row inserted, event published to test broker). Use Testcontainers Kafka/RabbitMQ.
  • “Test pyramid is dogma” — adapt to your stack.
  • 100s of mocks per test — refactor or rewrite as integration.
  • Sleeps to fix race conditions — use proper sync primitives.
  • Mocking the thing under test.
  • Skipping tests for “later”.
  • Untyped mocks — break silently when API changes.
  • Big monolithic E2E suite that takes 1h and tells you “something failed somewhere”.
  • Where to draw the unit/integration boundary.
  • How much mocking is too much.
  • When E2E is worth its cost.
  • How you keep tests trustworthy at scale.
  • Whether contract tests replace integration tests (often yes for service-to-service).