Mock or isolation frameworks are the bread an butter of advanced unit testing scenarios.
An isolation framework is a library that allows you to isolate the unit of work from its dependencies by having the possibility to create and configure dynamic stubs and dynamic mocks at runtime. The classes are generated dynamically at runtime so it is simpler, faster and shorter than writing these fake objects by hand and having to manually write repetitive code for these fakes. These dynamic fake objects can be configured to simulate fake values. It simplifies the testing life as a developer by making the tests more readable and maintainable.
The art lies in knowing when to use dynamic versus hand-written mocks.
Some framework have more capabilities than others. Based on these capabilities, they can be groups into constrained and unconstrained frameworks.
Constrained frameworks generate code and compile it at runtime by inheriting and overriding interfaces and base classes so they are constrained by the compiler and intermediate language abilities just like hand-written code, hence its name constrained. This is why these types of frameworks cannot fake among others static, non-public and non-virtual methods, sealed classes and classes with private constructors. Some examples include Rhino Mocks, NSubstitute and Moq.
Unconstrained frameworks in .Net are profiler-based, they use the profiler APIs wrapped around the CLR that provide events on what happens during code execution. Some of these events allow changing and injecting new code to be compiler in memory which enables us to add new functionality to existing code. These events happen on all the code, including private or static methods and third-party code, so these unconstrained frameworks can inject and change any code. Some examples of such frameworks include Typemock Isolator and Moles or MS Fakes.
Unlike with constrained frameworks, you can write unit tests for untestable code with unconstrained frameworks and you can fake third-party systems that you cannot control so your design is not constrained by the tool or in this case by the isolation framework. Unconstrained frameworks are perfect for faking and testing legacy code which cannot or will not be refactored. On the other side, using unconstrained frameworks have some performance penalty by adding additional calls to the code and making the code slower and they can have the disadvantage that the tests become unmaintainable.
Frameworks present some design antipatterns:
- Concept confusion or mock overdose: hurts readability by having more than one mock object in a test, or the framework cannot distinguish between mocks and stubs.
- Record and replay: creates bad readability by the reader having to look up and down the code to understand the test. Use AAA-style mocking instead.
- Sticky behavior: the test won’t have to know about how the method has to behave when the framework can add a default behavior and the method will always behave this same way.
- Complex syntax: can cause friction in the coding experience.
Isolation frameworks allow easier creation of fakes and easier verification of parameters and multiple method calls. It’s important to know when the frameworks hinders development more than it helps by avoiding unreadable test code, verifying the wrong things, having more than one mock per test and over-specifying the tests. When the code using a framework starts to look ugly, it’s best to simplify things.
Resource for this article
- The Art of Unit Testing, Roy Osherove