
You can't mock what you new up
On this page
I sat down to unit-test a .NET service and hit a wall in the first hour. The class
I wanted to fake was constructed with new inside the method under test. There was
no seam. Nothing to swap.
You can’t mock what you new up. The object is welded to the code that made it, and
a test has no way to slip a stand-in through the gap because there is no gap. That
matters beyond tidiness: code you cannot test is code you cannot change with
confidence, so the bugs it hides tend to reach real users unchallenged.
I could have rewired the whole dependency graph to inject every collaborator. On a service with a dozen of them, that is a big, risky change to land just so I can write one test. So I did something smaller.
Draw the seam at the edge of your own logic#
Most of the interesting behaviour was not in the external call. It was in the code around it: the guard clauses, the selection of which items to send, the windowing that decided the batch. All of that was mine, and all of it was testable without touching the collaborator at all.
So I stopped trying to mock the thing I could not reach and drew the test seam right up to the boundary of my own logic. I tested every decision the service made before the external call, and asserted on the arguments it was about to hand over.
The external call itself I treated as the edge of the map. Not my code, not my test’s job to prove it works. My job was to prove I fed it the right thing.
Put the seam at the boundary of your own logic and test up to the external call. Everything past that line belongs to someone else’s tests.
One virtual method beats rewiring everything#
One collaborator I genuinely did need to observe. It was doing something I had to confirm happened. Rewiring it through the constructor was possible, but it would have touched a lot of call sites.
Instead I marked a single method virtual. That was the whole change. A virtual
method is a seam you can override, so in the test I subclassed the collaborator with
a capturing double that recorded what it was asked to do and otherwise behaved
normally.
// production keeps the real behaviourpublic virtual void Dispatch(Payload p) { /* real work */ }
// the test double just captures the callclass CapturingClient : RealClient { public Payload? Seen; public override void Dispatch(Payload p) => Seen = p;}One keyword, behaviour preserved, and I got the observation I needed. Prefer the smallest seam that does the job over an architecture change you are doing only to satisfy a mock.
A sequenced fake for a multi-step API#
The harder case was a provider flow with four steps: create a job, enqueue it, poll for the result, parse it. One canned HTTP response cannot model that, because each step expects something different back.
So I built a sequenced HTTP test double. It holds a queue of canned responses and returns the next one on each call. Step one gets the create response, step two the enqueue response, and so on.
The catch that bit me: the first real call was not any of my four steps. It was auth, the token acquisition every request needs before it does anything. My sequence was off by one until I put the token response at the front of the queue.
var responses = new Queue<HttpResponseMessage>(new[] { TokenResponse(), // the call I forgot CreateResponse(), EnqueueResponse(), PollResponse(), ParseResponse(),});A sequenced fake models a real multi-step API far better than a single response, because it exercises the code’s assumptions about order, and it forces you to account for the invisible first call you keep forgetting is there.
You cannot mock what you new up, so stop fighting it. Move the seam to the edge of
your own logic and test everything up to the external call. When you do need to reach
one collaborator, one virtual method is usually a cleaner seam than rewiring a
graph. And when the thing you are calling has several steps, a sequenced fake tells
you the truth that a single canned response never will.


