Skip to content

Testing Strategy — Detailed#

Layers#

Layer What it covers Speed Brittleness
Unit one function / class in isolation ms low
Component one service end-to-end in-process tens of ms low
Integration service + real DB / queue / cache hundreds of ms medium
Contract API consumer ↔ provider expectations ms low
E2E browser / mobile through whole stack seconds-minutes high
Performance / load latency, throughput, soak varies n/a
Chaos failure injection varies n/a

Test doubles#

classDiagram
  class Dummy {
    +never used
  }
  class Stub {
    +returns canned data
  }
  class Fake {
    +real-ish working impl<br/>e.g. in-memory DB
  }
  class Spy {
    +records calls
  }
  class Mock {
    +pre-programmed expectations<br/>verifies usage
  }
Term Behaviour Verifies usage? Common library
Dummy filler argument no n/a
Stub returns fixed values no hand-coded
Spy records calls, default behaviour optional Mockito spy, jest
Mock expectations programmed up-front yes Mockito mock, jest.mock
Fake in-memory working impl no hand-coded (in-mem repo)

Rule of thumb: prefer fakes, fall back to stubs, use mocks sparingly.

TDD cycle#

flowchart LR
  R([Red - write failing test])
  G([Green - write minimum code to pass])
  REF([Refactor - clean up, keep green])
  R --> G --> REF --> R

    classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
    classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
    classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
    classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
    classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
    classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
    classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
    classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
    classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
    classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
    class R,G,REF service;

Useful for: - New behaviour with a clear spec. - Bug-fix (write failing test first). - Algorithms with well-defined inputs/outputs.

Not useful for: - Exploratory UI work. - Spike code expected to be thrown away.

Contract testing#

flowchart LR
  CC[Consumer test]
  PACT[Pact file - JSON contract]
  PV[Provider verification]
  CC --> PACT --> PV

    classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
    classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
    classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
    classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
    classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
    classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
    classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
    classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
    classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
    classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
    class CC,PACT,PV service;

Pact / Spring Cloud Contract: consumer writes "I expect provider to respond like this"; provider runs the spec against its real handler. Catches API drift without coupling deploys.

End-to-end tests (judiciously)#

  • Cypress / Playwright / Selenium for browsers.
  • Detox / Appium for mobile.
  • One smoke test per critical user journey (login, checkout, send-message).
  • Run on every deploy; don't run on every PR (too slow).

Performance testing#

  • Load test: target QPS, sustained, measure p99.
  • Stress test: increase until failure; observe where + how.
  • Soak test: 24 h+ at production rate; find leaks.
  • Spike test: sudden bursts; verify autoscale + protection.

Tools: k6, Locust, Gatling, wrk, vegeta.

Coverage#

  • 80% line coverage is a floor, not a goal.
  • Branch coverage > line coverage in usefulness.
  • 100% is over-engineering; focus on the code path that matters.

Property-based testing#

prop_reverseTwice :: [Int] -> Bool
prop_reverseTwice xs = reverse (reverse xs) == xs

Generates many random inputs and asserts an invariant. Hypothesis (Python), fast-check (JS), jqwik (Java), QuickCheck (Haskell).

Mutation testing#

Run "mutants" of your code (flip > to <, etc.); a useful suite catches them. Tools: PITest (Java), Stryker (JS).

Anti-patterns#

  • Testing the framework: don't test that Spring autowires.
  • Mocking everything: brittle, low-value tests pass while the system is broken.
  • One giant integration test: long, hard to debug, slow.
  • Tests with sleeps: use awaitility / polling assertions.
  • Snapshot tests as the only safety net: large diffs become rubber-stamped.

Glossary & fundamentals#

Concepts referenced in this design. Each row links to its canonical page; the tag column shows whether it is a high-level (HLD) or low-level (LLD) concept.

Tag Concept What it is Page
LLD Testing strategy pyramid, doubles, TDD, contracts testing-strategy
LLD Behavioural patterns Strategy, Observer, State, Command, Chain behavioral-patterns