Testing Pattern-Based Code
Testing pattern-based code reveals whether patterns helped or hurt.
Introduction
Testing pattern-based code reveals whether patterns helped or hurt. Done well: contract tests at seams, in-memory fakes for ports, role-based mocks. Done badly: 200-line integration tests per implementation, fat-interface mock hell, tests that break every rename.
The story
Team replaced 200 lines of brittle integration tests with one 20-line contract test run against Postgres, in-memory, and Mongo implementations. Integration tests broke every refactor; contract test broke only when behavior changed.
The business problem
Wrong testing strategy makes patterns feel expensive:
- Flaky integration: real DB in every unit test.
- Mock explosion: fat interfaces violate ISP — 15 mock methods.
- Drift: duplicate tests per impl — Postgres passes, Mongo fails silently.
- Implementation tests: refactor breaks tests with no behavior change.
The problem teams faced
Testing patterns well requires:
- Contract tests at abstraction seam — one suite, all implementations.
- In-memory fake for Repository/Port in unit tests.
- Role interfaces — mock only methods test uses.
- Pyramid: many unit at domain, few integration at edges.
Understanding the topic
Intent: Test behavior at seams; implementations prove contracts.
- Contract test — shared suite against each impl.
- Fake — in-memory Repository, not mock framework theater.
- Test seam — inject port; unit test domain without DB.
- Characterization — legacy behavior pinned before refactor.
Internal architecture
Contract test for OrderRepository:
function orderRepositoryContract(makeRepo: () => OrderRepository) {it("finds by id after save", async () => {const repo = makeRepo()const order = Order.create({ id: "1", total: 100 })await repo.save(order)expect(await repo.findById("1")).toEqual(order)})// ... shared behavior cases}orderRepositoryContract(() => new PostgresOrderRepo(db))orderRepositoryContract(() => new InMemoryOrderRepo())orderRepositoryContract(() => new MongoOrderRepo(client))
Visual explanation
Three diagrams cover contract tests, pyramid, and fake vs mock:
Informative example
Before / after — duplicated integration vs contract:
// ❌ 200 lines duplicated per databasedescribe("PostgresOrderRepo", () => { /* 200 lines */ })describe("MongoOrderRepo", () => { /* 200 lines drift */ })// ✅ Contract + thin wiringfunction orderRepoContract(factory: () => OrderRepository) { /* 20 lines */ }orderRepoContract(() => new PostgresOrderRepo(testDb))orderRepoContract(() => new InMemoryOrderRepo())
Execution workflow
Identify seam
Repository, Strategy, Port.
Real-world use
Go table-driven tests across impls; Java Testcontainers + in-memory H2; hexagonal architecture testing guidance; contract tests in microservices (Pact) at API seam.
Production case study
200-line integration → 20-line contract:
- Before: brittle integration; broke on refactors.
- Contract: 20 lines × 3 implementations.
- Outcome: failures only on real behavior change.
Trade-offs
- Pro: fast unit tests; no impl drift; refactor-safe.
- Con: contract design takes upfront thought.
- Con: fakes must stay in sync with port — maintain fakes.
Decision framework
- Multiple impls of same port → contract test mandatory.
- Unit test domain with fake, not mock, at repository seam.
- Integration test at adapter boundary only.
Best practices
- Narrow port interfaces — small mocks, small fakes.
- Factory param for contract: makeRepo: () => Port.
- Don't test private methods — test seam behavior.
Anti-patterns to avoid
- Mocking entire fat UserService for one method call.
- Real Postgres in every unit test "for realism."
- Testing that Factory returns correct class — test behavior.
Common mistakes
- Fake too smart — reimplements production bugs.
- Contract too tied to Postgres SQL semantics.
Debugging tips
- Contract fails on one impl only → impl bug, not test bug.
Optimization strategies
- In-memory fake for local dev — sub-ms test runs.
Common misconceptions
- Patterns don't slow tests — bad tests at bad seams do.
- Mock ≠ always wrong — prefer fake at ports, mock at edges.
Advanced interview questions
Interview Prep
Practice concise answers, then expand each card for the explanation.
1IntermediateQuestionHow test Strategy pattern?+
Answer
Follow-up
2AdvancedQuestionContract test vs integration test?+
Answer
Follow-up
3IntermediateQuestionTest Repository without DB?+
Answer
Follow-up
Summary
You can design contract tests, fakes at ports, and explain the 20-line win. If you can do that, you own Testing Pattern-Based Code.