Skip to content

Real-World Test Architectures, Decision Factors, and Heuristics

Real-World Architectures

Microservices

Challenge Strategy
Many services, many contracts Consumer-driven contract tests (Pact)
Service A calls Service B Stub Service B in A's integration tests
Shared event schema Schema registry + event contract tests
Full flow requires 5 services One E2E per critical path; unit everywhere else

Contract testing workflow:

Consumer test → generates Pact file
                      ↓
          Pact Broker publishes contract
                      ↓
      Provider verification runs in CI
                      ↓
       Deployment gate: can-i-deploy check

Frontend Applications

Layer Pattern When
Component Unit test with DOM renderer Logic in component
Integration API mock + component rendering Data flow
E2E Playwright user journey Critical paths
Visual Screenshot diff UI regressions

Rules: - No business logic in UI components — test it at the service layer - Use data-testid for all interactable elements - API responses mocked with msw (Mock Service Worker)

Realtime Systems (WebSocket)

Concern Test approach
Message ordering Assert sequence IDs monotonically increase
Reconnect and replay Close connection, reconnect with last_seq, verify replay
Fan-out latency Measure time from publish to all subscribers receiving
Concurrent connections Spin up N connections, assert all receive the same event

Decision Factors

Test Speed

Need Choice
TDD cycle < 1 s Unit tests only
PR gate < 5 min Unit + API with mocks
Pre-release validation Full suite including E2E

Coverage

Layer Target
Unit 100 % of business logic
API 100 % of endpoints (happy + error paths)
E2E 100 % of critical user journeys
Contract 100 % of public API consumers

Maintainability

Factor Trade-off
More unit tests Fast, but don't test wiring
More integration Slower, but catch real bugs
More E2E Confidence, but slow + fragile
Contract tests Medium speed, prevents consumer breakage

System Complexity

Complexity Test strategy
Monolith Pyramid: unit → integration → E2E
Microservices Contract tests + isolated service tests
Event-driven Event contract tests + saga integration tests
Realtime Connection lifecycle + message ordering + reconnect

Engineering Heuristics (Senior+)

Prefer API tests over UI tests

The API is stable; the UI changes constantly. Test business logic at the API layer. Reserve Playwright for user journeys only.

Keep tests deterministic

Any test that can fail without a code change is a liability. Use frozen time, mocked randomness, and unique per-test data.

Avoid duplication

If the same assertion appears in 5 tests, extract it to a helper. If the same setup appears in 3 tests, extract it to a fixture.

Test behaviour, not implementation

Ask: "If I refactor the internals, should this test still pass?" If yes — it tests behaviour. If no — it tests implementation. Fix it.

Minimise flakiness

Every flaky test is a trust erosion. Track, quarantine, and fix flaky tests within one sprint of discovery.


Pattern Selection Guide

Scenario Pattern
UI test reuse Page Object Model
Multi-actor UI flows Screenplay
Complex test data Builder
Shared setup + teardown Fixture
API call abstraction Wrapper + Fluent Request
Verify side effects Mock
Isolate data source Stub / Fake
Parameterised scenarios Data-Driven + parametrize
CRUD flow reuse Test Template
Readable assertion chains Custom Assertion helpers