Tuesday, 1 September 2026

"Don't mock what you don't own" is a rubbish way of articulating good app design and testing advice

The practice is sound. The slogan is catchy, but the reasoning usually articulated is… "less good than it could be".

Look it up and you get roughly this:

Never write mocks for third-party libraries, frameworks or services you don't control. Wrap the dependency in your own adapter, and mock that adapter in your unit tests instead.

The justifications given are almost always (some subset of ~):

  • A mock of someone else's library encodes your assumptions about how that library behaves, not how it actually behaves.
  • When the vendor ships a breaking change, your mocks keep passing while production falls over.
  • Mocks of third-party APIs need piles of setup and are miserable to maintain.
  • API changes force you to rewrite mock configuration all over the test suite.

Then a nod to interfaces, ports and adapters, and everyone goes home feeling like they've learned something.

The second reason is the one people repeat most, and it's the one that doesn't survive being thought about.

If I mock my own adapter, my unit test still knows nothing about whether the vendor changed their API. The test passes. Production falls over. That is precisely the failure the advice claims to prevent, and moving the mock one layer inwards does not prevent it. A unit test cannot detect a change in a system it never speaks to. True whether the mock sits at the vendor's client or at my wrapper around it.

So the silent-production-failure argument doesn't get you to adapters. It gets you to integration tests, which are the only thing that catches a vendor's breaking change. And once you have those, what you happened to mock in your unit tests is beside the point.

If catching breakage were really the goal, the honest advice would be one of two other things. Either "don't mock third-party services at all, test against the real thing", or "mock for unit tests, and write integration tests as well". Neither of those is "don't mock what you don't own".

Take the bogus reason away though, and the practice still stands; just on different grounds.

You use a small slice of most third-party libraries. An SDK exposes fifty methods and you need two. An adapter hides the other forty-eight, and your unit tests only ever have to stand in for the two things your code actually does.

Vendor APIs are shaped for the vendor's convenience. Mocking stripe.customers().create().charges().create() means reproducing the library's internal mechanics in your test setup, so the test ends up asserting things about someone else's design decisions. payments.charge(amount) is one line, and it says what your code wanted to happen.

Vendor types spread. If their request objects, response objects and exception classes are visible in your business logic, they'll be visible in the tests for that logic, and then in every test file that touches it. When the API changes you go and fix all of them. Keep those types inside the adapter and you fix the adapter plus its integration test. The rest of the suite doesn't move.

Your domain deserves its own vocabulary. The vendor says "Account", your business says "Member". The adapter translates once, in one place, and your code and your tests both talk about Members.

Your code changes on your schedule. The library changes on theirs. The adapter is where those two schedules meet, so an upgrade lands in one file rather than forty.

Notice that none of that has anything to do with catching a vendor's breaking change. It is all about how much vendor-shaped detail is allowed into your codebase, and where the bits you can't avoid end up living.

Which suggests saying something more like this:

Wrap third-party dependencies in adapters that speak your domain's language. Integration test the adapter against the real service. Mock the adapter everywhere else.

Longer. Less catchy. Says what it means, and doesn't pretend a unit test can see something it demonstrably can't.

The slogan survives because it's short, and because repeating it sounds like expertise. That's how most of this propagates. Someone reads a heuristic, likes the shape of it, passes it on with the reasoning slightly garbled, and a few hops later it's a rule with a broken justification welded to it. Then a colleague asks why, you hand them the broken justification, and they put a hole in it in about ten seconds. Fair enough too.

Keep the practice. Bin the slogan, or at the very least bin the explanation that usually comes with it.

Thanks to my mate and namesake Adam Tuttle for pulling me up on this when I used it when describing a juncture I had between unit and integration testing I had. This make me think about why I say "don't mock what you don't own", and made me wonder if I was cargo-culting bad advice. No. The advice is sound. The "pop" way of articulating it a bit shite.

Righto.

--
Adam

PS: drafted by AI, polished by me. Also based on a "conversation" I had with a different AI. AI was used as a time-saving device, not a creative one.