How .Net Mocking Frameworks Work Under the Hood

Header Photo Credit: Lorenzo Cafaro (Creative Commons Zero License)

Unit testing has become an accepted part of our lives as .NET programmers. To help focus our tests to only the code we want to validate, Mocking Frameworks are a powerful tool in our toolbox. Like many tools, if you have an understanding of how the tool works under the hood, you can bend it to your will (and also know where it'll break if you bend too much).

In this post, I provide an overview of the two main types of mocking frameworks: constrained frameworks (like RhinoMocks and Moq) and unconstrained frameworks (such as Typemock Isolator and Telerik JustMock). I'll dig into how the two actually do their magic and some of the pros and cons of each approach.

Terms: Constrained vs Uncontrained

A few years back I read The Art of Unit Testing by Roy Osherove, and in one of the chapters he uses the terms "Constrained" and "Unconstrained" to describe the two main types of mocking frameworks, which I feel are excellent names for the two.

Constrained Frameworks
I'll dig into the "why" later in this post, but contstrained frameworks are "constrained" by the rules of class inheritance within .Net. This means the framework must have visibility to the members you want to mock (ie: you can't mock private members) and the ability to override them (ie: you can't mock sealed or non-abstract/non-virtual members).
Unconstrained Frameworks
Frameworks that fall into this category don't have the same limitations as Constrained frameworks and can pretty much mock anything you care to throw at it.

Contrained Frameworks: Class Inheritance On-Demand

Chances are, if you've used mocking frameworks in .Net, you've likely used one in this category. This is because most of them are free and open-source, so their barriers to entry are set pretty low. Frameworks in this category include Moq, RhinoMocks, NSubstitute, and FakeItEasy. In almost all cases, these frameworks use the Castle DynamicProxy library to do the heavy lifting.

The general idea behind contrained frameworks is that a new proxy class is created at runtime that extends the class (or implements the interface) you're mocking, adding code that will check for the mock behaviors, as well as some infrastructure code.

For example, if you have this class:

    public class MyClass
    {
        public virtual int GenerateNumber(){
            return DateTime.Now.Millisecond;
        }
        
        public virtual int GenerateNumber(int floor){
            return floor + DateTime.Now.Millisecond;
        }
    }

You can generate a mock of GenerateNumber() with code like this (using RhinoMocks syntax):

    var mock = MockRepository.GenerateMock<MyClass>();
    mock.Expect(x => x.GenerateNumber()).Return(11);

Conceptually, when this code runs, it would generate a new proxy class that looked something like this:

    public MyClassMock: MyClass
    {
        public override int GenerateNumber() {
            return 11;
        }
    }

Now, imagine you generated a mock with this syntax:

    var mock = MockRepository.GenerateMock<MyClass>();
    mock.Expect(x => x.GenerateNumber(Arg<int>.Is.GreaterThan(10)))
        .Return(11)
        .Repeat.Twice;

The resulting generated code would start to get more complex -- something like this:

    public MyClassMock: MyClass
    {
        private callCounter = 0;

        public override int GenerateNumber(int floor) {
            
            //Check the conditions:
            if (floor > 10) //Arg<int>.Is.GreaterThan(10)
            {
                callCounter++;
                if (callCounter < 2)  //.Repeat.Twice
                {
                    return 11;
                }
            } 
            
            //All other cases, use the real implementation.
            return base.GenerateNumber(floor);            
        }
    }

If you create a strict mock, any unmocked methods would end up with something like this:

    public override int GenerateNumber() {
        throw new NotImplementedException("Not mocked");
    }

Or for a dynamic mock, like this:

    public override int GenerateNumber() {
        return default(int);
    }

But certainly, it ends up being a lot more complicated. For example, since you later will likely want to call mock.VerifyWasCalled(...), the proxy method will need to maintain information about when it was called and what parameters were provided. You as the caller could have multiple mock implementations based on input args, etc, etc. So there's a fair amount of additional logic that goes into these proxies, but you get the general idea.

The main thing to take away is that the proxy objects used to implement the mocks rely on inheritance to put a middle layer between the calling code and the class being mocked. Because of this, only things that can be inherited can be mocked. Classes that are sealed and members that are static or private cannot be mocked. Members that are internal can be mocked, but only if you add an [InternalsVisibleTo(...)] attribute for the proxy assembly. Since almost all of these frameworks use Castle DynamicProxy (which generates an in-memory assembly named "DynamicProxyGenAssembly2"), that would look like this:

[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]

Impact on the design of your own classes

In many cases, in order to fully test your own classes and mock the external dependancies, you will find yourself in a situation where you have to make design tradeoffs to allow the member to be mocked. For example, you may need to make something virtual or internal that you would otherwise not expose for modification or use outside the class. So you must make the choice between testablity and usability of the classes. This can be a hard choice to make.

Uncontrained Frameworks: Make Your Own Man-In-The-Middle Attack

While Constrained frameworks must live within the rules of .Net inheritance, Unconstrained frameworks have almost no limitations as to what they can mock. This is because unconstrained frameworks don't create new classes -- instead, they modify the existing code, even if it's not your code, at runtime to inject their own logic.

To do this, they utilize the .Net Profiling APIs. Using these APIs, a "profiler" (in this case, the mocking framework) registers with the .Net CLR so that the CLR executes a callback within the profiler every time the CLR loads a class, compiles a function, etc. These frameworks will then wait for the JITCompilationStarted callback, which the CLR will use just before it JIT-compiles something. The callback can then use the ICorProfilerInfo::SetILFunctionBody to modify the MSIL for the method being JIT-compiled and inject it's own logic.

Frameworks in this category include Telerik JustMock, Typemock Isolator, and Microsoft Fakes. These are all (to the best of my knowledge) closed-source, commercial (licensed) software. Since most of these frameworks are closed-source, and their license do not permit me to decompile their assemblies, I'm making some guesses here as to how they are implemented, but for the purposes of understanding how they work, I believe this is fine.

Going back to our example class:

public class MyClass
{
    public virtual int GenerateNumber(){
        return DateTime.Now.Millisecond;
    }

    public virtual int GenerateNumber(int floor){
        return floor + DateTime.Now.Millisecond;
    }
}

You can generate a mock of GenerateNumber() with code like this (using Typemock Isolator syntax):

    var mock = Isolate.Fake.Instance<MyClass>();
    Isolate.WhenCalled(() => mock.GenerateNumber()).WillReturn(11);

When the MyClass.GenerateNumber() method is being compiled by the CLR JIT-er, it will call into the profiler (in this case, a Typemock Isolator service that is running), which will re-write the code to insert some hooks, like this:

    public MyClass
    {

        private bool _mockWasProvided() 
        {
            // Would determine if a mock was configured
            // for this specific method call or not.
            // This may need to consider argment filters,
            // "Repeat.Once" directives, etc.
            return true; 
        }

        private int _userProvidedMock() {
            // This would be the user-configured
            // mock behavior.
            return 11;
        }

        public virtual int GenerateNumber() {
            
            //START injected code
            if (_mockWasProvided())
            {
                return _userProvidedMock()
            }
            //END injected code
            
            return DateTime.Now.Millisecond;
        }
    }

Just like the examples I provided in the constrained frameworks, this can get pretty complicated, as it needs to deal with advanced input matching filters, capturing data needed to verify calls were actually made using Isolate.Verify.WasCalled(...);, etc.

An important thing to note: In order for these to work, a profiler application must be running and registered with the CLR's Profiler API before any of your code is loaded into the AppDomain, otherwise it will already be JIT'd and your mocks won't be injected. This generally requires a service or application from the mocking framework be installed on the system and running in the background. Since it's basically listening in to every CLR action, this will need to run with elevated privileges, which could represent a security risk. In effect, you are intenationally building your own man-in-the-middle attack against the code you want to mock. This also means you cannot run code that utilizes Unconstrained mocking frameworks unless you have the profiler installed.

Mocking Third-party and Static Code

One big advantage that comes with the Unconstrained frameworks is that you can mock pretty much anything, since the CLR will call into the profiler when any code is being JIT-compiled. This means you can provide mocks for static, sealed, private, etc, members. It also means you can create mocks for Third-party code or even the .Net Framework itself. As you can imagine, you can get yourself into some hot water here. If you provide a mock for a very commonly used .Net framework member and your mock has really bad performance, you're going to have a very negative impact on the performace of everything! For this reason, most of these frameworks don't let you mock out certain very commonly used / high-performance .Net framework code, such as the string constructor.

To put this into concreate examples, think about the ability to mock out calls to DateTime.Now so that you can write unit tests against the code without having to move that into a seperate, mockable (non-private, virtual) method. You could unit test for Leap Year scenarios, end-of-month reports, etc:

  Isolate.WhenCalled(() => DateTime.Now).WillReturn(new DateTime(2016, 2, 29));

Other common scenarios include:

  • SharePoint and EntityFramework SDKs
  • HttpContext objects in ASP.NET
  • classes using the Singleton pattern.

Putting it into Perspective

Constrained frameworks can cover a major portion of most peoples needs, especially for greenfield (new development work) projects and have the major benefit of being free. Unconstrained frameworks can cover just about anything and remove the need to expose parts of your code just so they can be tested. But they can be fairly expensive.

Here's the big breakdown, in table form.

Constrained Unconstrained
Members:
methods
properties
events
static
sealed
Access:
public
internal
private
Features:
Your own code
Third-Party Code
.NET Framework
Open Source
License Open Source Proprietary
Cost Free $$$*
Additional Software Profiler Application
Commercial Support Limited With Paid License
May force design tradeoffs
Best fit for code written with testing in mind. Great for testing "legacy" code.

* At the time I wrote this, prices were as follows:

  • Telerik JustMock: $399 per developer
  • Typemock Isolator: $399 per developer, $990 per 5 build servers
  • Visual Studio Enterprise (MS Fakes): $5,999 per developer ($2,569 renewals). MS Fakes is only available as part of VS Enterprise and is not sold seperately.