Design Patterns Tutorial 0/70 lessons ~6 min read Lesson 59

    Testing Pattern-Based Code

    Testing pattern-based code reveals whether patterns helped or hurt.

    Course progress0%
    Focus
    21 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    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:

    ts
    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:

    Contract test pattern
    Shared test suite
    Behavior
    Postgres impl
    Run suite
    In-memory impl
    Run suite
    Mongo impl
    Run suite
    One 20-line suite × 3 impls beats 200 lines × drift.
    Test pyramid with patterns
    Many unit
    Domain + fakes
    Some integration
    Adapter
    Few E2E
    Full path
    Seams enable unit
    Ports
    Patterns at seams make pyramid possible — if seams are narrow.
    Fake vs mock
    Repository port
    Interface
    InMemoryRepo
    Fake — real logic
    jest.fn() × 12
    Mock hell
    Prefer fake
    At port
    Fake implements port with Map — tests behavior not call order.

    Informative example

    Before / after — duplicated integration vs contract:

    ts
    // ❌ 200 lines duplicated per database
    describe("PostgresOrderRepo", () => { /* 200 lines */ })
    describe("MongoOrderRepo", () => { /* 200 lines drift */ })
    // ✅ Contract + thin wiring
    function orderRepoContract(factory: () => OrderRepository) { /* 20 lines */ }
    orderRepoContract(() => new PostgresOrderRepo(testDb))
    orderRepoContract(() => new InMemoryOrderRepo())

    Execution workflow

    1Testing at seams workflow
    1 / 4

    Identify seam

    Repository, Strategy, Port.

    Where pattern boundary is.

    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.

    3 questions
    1IntermediateQuestionHow test Strategy pattern?+

    Answer

    Unit test context class with fake strategies or direct impl instances. Test each strategy's algorithm separately. Integration test wiring only if needed.

    Follow-up

    Contract for Strategy?
    2AdvancedQuestionContract test vs integration test?+

    Answer

    Contract: shared behavior suite all implementations must pass — defines port. Integration: full stack with real infra — fewer, slower.

    Follow-up

    20 vs 200 line story?
    3IntermediateQuestionTest Repository without DB?+

    Answer

    InMemoryRepository implementing same port — domain unit tests use fake. Contract test runs against memory + real DB in CI.

    Follow-up

    Fake maintenance?

    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.

    Ready to mark this lesson complete?Track your journey across the entire course.