Freigeben über


To mock or not to mock, that is the question! (or, when to use dependency injection)

When unit testing, I often use the terms "mocking" and "dependency injection" interchangeably. Now, I know that they are different, however, given that a mock object assumes some sort of dependency injection is involved, I hope you'll forgive me. However, dependency injection without mocking implies that you're not unit testing, and that's just plain wrong.

Also, if we're stuck with a static typing language and testing with a class that can't be easily mocked (no interface or super-class to grab onto for mocking purposes), we could actually be talking about a testing isolation framework, where we are able to present substitute objects for those objects that can't normally be mocked, usually via some code-injection technique (like TypeMock or Microsoft Fakes Shims), and still loosely refer to that as mocking.

So when testing, when do we choose to mock (either classic mock/stub or isolation) and when should we leave well enough alone to avoid mucking up both the test and production design?

Let's take a classic object-oriented example, such as with a "Car" object. A car object might have an engine object, an electrical system object, wheel objects, among others. In the simple case, we'd want to leave the object graph alone and not introduce a bunch of dependency injection and mock objects in the tests. By "simple case", we mean that there is no need to have special classes that represent a carbon fuel engine, another class to represent an electric engine, and yet another class that might represent a "hybrid" engine, which might all represented by an IEngine interface (in the case of C#). By the way, if there's no immediate requirement for such an IEngine interface, but you think it might be useful in the future, just hold on, hombre - You aren't gonna need it (YAGNI). Don't add code unless it's an absolute requirement, and make it as simple as possible.

So in the case of a simple Car object and a simple Engine object, just skip the dependency injection and mocking the engine. Introducing dependency injection is not "as simple as possible", but rather, it introduces additional complexity.

"But, but, but... who's going to create the Engine object?" you might ask in a querulous voice. Simple (see what I did there?), let the Car create the engine.

(Gasp!)(sputter!) "But that's agile and object oriented blasphemy!" Meh... not so much. Remember, this is just a simple set of objects, which is how it should always start out anyways. Until dependency injection is an actual requirement (and one that's not simply in your own mind), You Aren't Gonna Need It!

So in the case of simple component objects, let the composite object construct its own simple component objects. Your code and tests should become much simpler.

You say, "But then I 'm not isolating my unit tests to a single object! How will I know where something breaks?" Well, given that you're practicing TDD (and you are, aren't you), then you'll know that the problem was with the last few lines of code you just wrote, right?

"So, then perhaps it is better to not mock at all," you may well say. But let's not throw out the baby with the bathwater, as the saying goes. There are plenty of situations where dependency injection and mock objects are useful, such as in the case of a true interface requirement, where multiple implementations to fulfill a particular contract (interface) are required, or when crossing process boundaries, thus causing side-effects from your unit tests (a big no-no) and making your unit tests run slowly, which can be a drag in itself.

Bottom line, if you're introducing an interface for no other reason other than it's what you always do when you're testing, then please reconsider. Is there a way to simplify your design such that it doesn't require mocks? But if you're:

  • working with interfaces for more than just testing reasons,
  • or your component objects cross process boundaries,
  • or calls against your component have are not idempotent (likely using a shim)

...then by all means, use dependency injection or a shim.