I’ve finally gone a full year without mocking a single thing in my tests or using a mocking framework and all for the better I think. The majority of my test-driven-dotnet life has seen me using Moq, primarily, with a bit of FakeItEasy and one time Rhino Mocks (in anger) up to this point. A while ago I started really wanting to shake them off and try different patterns.

But it was an uphill struggle for a while, really - I’ve made mistakes while coming down this road, struggled with alternatives, failed to get team buy-in et cetera. Fast-forward to now, where I’m part of a team delivering (imo) fairly well-tested software without a mock in sight. I am very thankful they’ve been able to lead me to more clarity down this path.

Why?

A testing strategy that leans heavily into mocking also tends to lean into testing interactions (over behaviours/requirements/state), and shies away from testing real coupling (where you can’t avoid it). I feel mocks are often detrimental to a codebase in a few ways including this.

Testing interactions tests something, sure, but I think the cost-to-value is just not good enough for me. Those interactions can change often in ways that don’t really matter to actually important features/behaviour.

Parts of the software that can be unit tested in complete isolation naturally can be without mocks and classes that don’t really provide much value in isolation are better tested in conjunction with something real (and it’s better verifying the output, not the internal interactions). When I say “better” and “valuable” here, I mean that by shifting my focus I believe I have prevented more actual bugs and guarded against more actual regressions than with a mocking-and-interaction based approach, as mocks can very easy misrepresent realistic dependencies, or be really costly to do so.

In addition to this, interfaces in C# are a powerful tool (although the cowards at Microsoft should give me ducktyping) which get completely overlooked in order to serve overmocked codebases, becoming nothing more a heap of noise and files (which, in my experience, leads people to rarely design interfaces on purpose). In your codebase, if every NamedClass with nontrivial behaviour has a carbon copy of its entire public API in an INamedClass, and if every constructor parameter it takes is similarly an IFullApiOfOtherClass, you’re already there.

But why is that a detriment? Why is it litter, and not just simply the Cost of Business to serve getting your code tested? It’s important to remember testing without mocks is entirely possible, one of many strategies, and not an unavoidable fact of simply having testable code. Similarly, DI is a powerful tool, but don’t let your test code dictate how you do it.

Mocks aren’t real, and often you’ll set them up over and over and over again with behaviour and expectations that might not quite match reality or prove anything important and at best that can be a lot of nonsense, at worst it is a maintenance headache. Sure you can employ patterns to manage this, but that costs more than you’re already spending, and I feel mocking has a ceiling of value that doesn’t justify it.

It doesn’t have to be like this.

How?

I believe you should value and do other things instead:

  • Test real things more. Integration, component, acceptance testing over solely “unit”.
  • Test as much of your code as possible. Don’t just mock away an integration point and never test that.
  • Understand that two things coupled together can often just be “an implementation detail” and you should strive to test the “unit” or behaviour that that coupling solves instead of both things in isolation.
  • Understand that looser coupling leads to code you can naturally test without mocking and that mocking can just make it easier to ignore these smells.
  • Fight the good fight on using interfaces with reason and design.
  • Build your own set of test doubles with actual behaviour.

Where possible today, anything at the boundaries of my application (database access, external API call, AWS service integration, system calls) is typically integration tested against the real thing where possible. If not possible (like, it’s rude and/or expensive and/or brittle to automate tests against a third party API), then some suitable alternative that allows us to test as much as possible (like using Http Interception so that we still validate our code which depends on HttpClient).

We may maintain our own “fake” implementation of such dependencies which I found has multiple advantages over mocking:

  • It is much cleaner, over mocking, to emulate behaviour, and that setup gets out of the way of your core test.
  • If you design these dependencies right, your fakes tend to be very small and not a lot of work anyway.
  • If you’re writing new tests using an old dependency, you have a ready-to-go double that’s already tried-and-tested.
  • Mocking has often required me to understand the nuances of the functions I’m interacting with, in order to setup expectations or assert interactions. Fakes haven’t required me to know this stuff unless I’ve written them, like most code I can freely use the public API without having to know the internals.
  • If lots of tests rely on a single concrete test double, new changes to the behaviour of that dependency (and the double) will automatically benefit and apply to all tests and help surface issues you may have. Refactoring things tends to have less of a ripple effect that necessitates changing a ton of tests unless the core requirements change.
  • They can be useful outside of automated tests - if you have a dependency you do not have access to, or are difficult to use locally you can often wire-up a double instead of the real thing locally.
  • If you’re building a library, you can ship fakes that actually reflect the library behaviour and consumers can consume those fakes in their tests too.

Not a one-size-fits-all, but a very worthwhile consideration if you’re making the move away from mocking yourself, it’s a strategy that has gone from an exception to a norm for me. I also feel much more comfortable adding an interface to add a test double as a second implementation, since it is a real thing with real behaviour that is expected to conform to the interface all the same. Not just a bunch of wiring designed to test interactions with a contract. In addition, might I suggest depending on functions over classes sometimes?

And for all the code between the boundaries? Favour not faking anything. You can unit test parts of it in isolation, you can test some parts as a “unit provided by a couple of dependencies”, you can test from boundary-to-boundary with all real objects between. Test things you care about (“When I call this API with an invalid body, I get validation errors in response”), not things that are inconsequential (“When I call this API, it passes the request body to the validator”).

So?

So, like I said above if you’re feeling a lot of pain or dissatisfaction with mocking in your tests, it doesn’t have to be like this, and if you haven’t ever seriously tried to build and test something without mocking I heavily suggest you give it an earnest try and see if you see the benefits I have done by shifting my approach.

In short I recommend eliminating mocking by changing your code strategy and values overall to not require it, and where you do still need a test double, consider alternatives and the value they provide.

Other reading