Testing Strategy — Theory
Testing Strategy — Theory (interview deep-dive)
Section titled “Testing Strategy — Theory (interview deep-dive)”Test behavior, not implementation
Section titled “Test behavior, not implementation”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).
When to mock vs use real
Section titled “When to mock vs use real”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.
Contract testing (consumer-driven)
Section titled “Contract testing (consumer-driven)”Microservices integration via Pact (or similar):
- Consumer writes a test asserting the calls it makes and what it expects back. Pact records this as a contract file.
- Provider runs the contract against its own service in CI, verifying it satisfies expectations.
- 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.
Flaky tests
Section titled “Flaky tests”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.
Test data strategies
Section titled “Test data strategies”- 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.
Mutation testing
Section titled “Mutation testing”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.
Property-based testing
Section titled “Property-based testing”Specify invariants; framework generates inputs.
// fast-checkfc.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.
Performance testing
Section titled “Performance testing”- 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.
Common interview Qs
Section titled “Common interview Qs”- 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.
- How do you keep integration tests fast? Containers shared across test files; transaction rollback; parallelism with isolated schemas; pin DB version.
- What’s flaky test policy? Block PRs from quarantine bin; fix or delete in N days.
- Why might 100% coverage still be insufficient? Tests can be coverage without assertions; mutation testing / boundary cases not exercised.
- How to test code that calls a third-party API? Use wiremock / msw / a contract test against a sandbox. Don’t hit prod.
- What is Pact, and where is it useful? Consumer-driven contract — verify provider without joint deploy.
- TDD — when do you not use it? Spikes / exploration; throwaway code; UI prototyping. Use after design clarifies.
- How to handle non-deterministic features in tests? Inject clock, RNG, IDs; freeze time for snapshot.
- How to test event-driven system? Verify side effects (DB row inserted, event published to test broker). Use Testcontainers Kafka/RabbitMQ.
Anti-patterns
Section titled “Anti-patterns”- “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”.
Decisions you should be able to defend
Section titled “Decisions you should be able to defend”- 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).