← All skills

Test the Seam

Points the tests at the boundaries where things actually break, instead of one test per function you happened to write.

testingboundariesintegrationmocksminutes

When to reach for it

When you are about to generate a test per function and suspect none of them would have caught last week's incident — or when a change adds an API call, a queue, a job or a new table.

What changes

  • You get a written list of the boundaries this change crosses — network, database, queue, clock, file system, config — and a test aimed at each, instead of a test aimed at each function.
  • Boundary tests assert the thing that crosses: the exact request body, the exact stored row, the exact published message, so a silently renamed field turns the suite red.
  • Failure behaviour at each boundary gets covered on purpose — timeout, rate limit, duplicate delivery, partial page — rather than only the success path.
  • The mock count drops, because the boundary is faked once at the edge instead of stubbed in twenty unit tests.
  • A failing test names a layer you can point at, so debugging starts at the right place rather than in the middle.

Pairs with

SKILL.mdpaste into your agent

Test the Seam

Systems break where they meet something else. Put the tests there.

1. List the seams this change crosses

Walk the change and name every boundary it touches:

  • Calls out to other services, and calls coming in
  • Database reads and writes, including what a partial write leaves
  • Queues, jobs, webhooks and anything delivered at least once
  • Serialisation: dates, decimals, encodings, null versus missing
  • The clock, the timezone, the locale, the source of randomness
  • The file system, the environment, configuration and secrets
  • Identity and permission checks
  • The transaction boundary — what commits together, what does not

2. Rank them by how quietly they fail

For each seam, ask what happens if its shape changes and nothing crashes: a renamed field arriving as undefined, a number arriving as a string, a timestamp arriving without a zone. Seams that fail quietly go first; loud failures find themselves.

3. Test each top seam at its contract

Assert the thing that crosses, not the code either side of it:

  • The exact request body, headers and method sent outward
  • The row that exists afterwards, field by field for fields that matter
  • The message published, including its key and ordering guarantee
  • The behaviour on the failure this boundary really produces: a timeout, a 429, a duplicate delivery, a half-page of results

4. Cover the middle with a table

Pure logic between the seams — parsing, pricing, formatting, state transitions — takes one table-driven test with the rows that matter: empty, one, many, the boundary value, the invalid one. No mocks belong in here.

5. Leave the rest alone

Code with no branch and no boundary does not need a test of its own. It is already exercised by the seam tests that run through it.

Rules

  • Do not stub the thing under test. If the subject is the HTTP client, fake the transport underneath it, never the client itself.
  • Do not write a test whose only assertion is that your fake behaved like your fake.
  • Do not put a live third-party call in the fast suite. Pin the contract with a recorded payload, and run a separate scheduled check against the real service so drift still surfaces.
  • Do not fake your own database when the query is the risk. Run it against a real one.
  • Do not add a test per function to satisfy a convention. One test at a seam that can break is worth twenty that cannot.