Skip to content

Displaced Truth

You’ve added tests at an internal boundary (a service, a helper, an extracted function) and over time those tests become the artifact your team trusts as proof of correctness — instead of tests at the application’s actual boundary (the UI, the HTTP API, the user-visible behavior).

Primarily cognitive, with a meaningful behavioral tail. The team’s belief about what is verified diverges from what is actually verified at the boundary that matters.

  • Code review and CI gate on the internal-boundary tests. They pass. The feature still ships broken at the actual boundary, because nothing tested the integration.
  • Refactoring the internal boundary becomes scary because “the tests” are pinned to the wrong shape — even though the application could absorb a different shape without anyone noticing.
  • New work that should produce a boundary test doesn’t, because “we already have tests for the service it calls.” The team has imported the testing pyramid as a justification for skipping the test that would actually catch bugs.
  • Bugs that escape are usually integration bugs — the units passed their tests but lied to each other in ways the unit tests couldn’t see.
  • A new feature is shipping; the test plan reads “I added unit tests for the new service” and stops there.
  • “We don’t need an end-to-end test for this; the service tests cover it.”
  • A bug ticket asks for a regression test and the proposed test is at the unit level, even though the bug was reported by a user.
  • High unit test count, high coverage, ongoing escapes of bugs that look like integration mismatches.
  • Production bugs that none of the existing tests would have caught even if they’d been run on the buggy code.
  • Tests that mock things that exist in the same application. (When you mock a thing your team owns and that thing changes, your test still passes — the mock is now lying.)
  • The team’s primary test commitment lives at the application’s actual boundary. UI tests via Cypress/Playwright. HTTP API tests via Alba / supertest / integration test runners. Domain tests for intrinsic-contract code (see False Reuse) may live below that.
  • Internal-boundary tests are scaffolding for development feedback loops, not certification of correctness. Be willing to delete them when their feedback value is gone.
  • When mocking, ask: “Is the thing I’m mocking owned by my team?” If yes, prefer the real collaborator (sociable test) over the mock (solitary test). See Sociable vs Solitary tests in reading-list.md.
  • Track escape rate by surface, not coverage. If most escapes happen at the integration boundary, that’s where the testing investment should concentrate, regardless of how the testing pyramid is drawn.
  • The internal boundary has an intrinsic contract worth pinning (Luhn check, parsers, etc.). Tests there are the certification, because the contract is the truth.
  • Performance-sensitive code where the unit-level tests measure something that isn’t visible at the boundary (allocations, latency at scale).
  • Library code being developed inside a monorepo. The library’s tests are the contract; the application that uses it has its own boundary tests.