Skip to content
← All posts

Less mocking, more confidence

·1 min read·

Most of the test suites I’ve inherited share a pattern: heavy unit tests, anemic integration tests, and an embarrassing number of bugs that ship despite green CI.

What mocks actually buy you

Mocks make tests fast and isolated. Those are good properties — but they’re not the same as correct. A mocked database call doesn’t care if your query is wrong. A mocked HTTP client doesn’t notice when the API contract changes.

The bugs that escape to production almost never live inside a single unit. They live at the seams.

What integration tests buy you

An integration test that spins up a real Postgres, runs the same migrations production runs, and exercises a real HTTP handler will catch:

  • Wrong SQL
  • Missing indexes (well — performance, not correctness, but you’ll feel them)
  • Auth bugs at the middleware boundary
  • Migrations that work on an empty DB but fail on real data

None of those are things a unit test would find.

The investment

The usual objection is "integration tests are slow and flaky." Both are solvable:

  1. Use a fast database driver and a per-test transaction that rolls back. Sub-second setup.
  2. Run tests in parallel against separate schemas. Most ORMs support this.
  3. Use a real container in CI (postgres:16-alpine boots in 2s).

A one-time investment of a couple of days, and the suite goes from "flaky and slow" to "reliable and fast enough."

My current default

A pyramid that’s been inverted: 70% integration tests, 20% unit tests for genuinely complex logic, 10% end-to-end. The bug-escape rate dropped, the suite stayed fast enough, and I stopped having to write vi.mock(...) calls I’d immediately forget about.

About the author

Kevin Shelley

Engineering Manager bridging people and technology — leading teams that ship production e-commerce while driving adoption of the tools that keep them ahead. See the portfolio · Get in touch.