How to mock sealed classes and static methods
14 Aug 2014 - 1313 wordsTypemock & JustMock are 2 commercially available mocking tools that let you achieve something that should be impossible. Unlike all other mocking frameworks, they let you mock sealed classes, static and non-virtual methods, but how do they do this?
Dynamic Proxies
Firstly it’s worth covering how regular mocking frameworks work with virtual methods or interfaces. Suppose you have a class you want to mock, like so:
public class TestingMocking
{
public virtual void MockMe()
{
..
}
}
At runtime the framework will generate a mocked class like the one below. As it inherits from TestingMocking
you can use it instead of your original class, but the mocked method will be called instead.
public class DynamicProxy : TestingMocking
{
public override void MockMe()
{
..
}
}
This is achieved using the DynamicMethod class available in System.Reflection.Emit, this blog post contains a nice overview and Bill Wagner has put together a more complete example that gives you a better idea of what is involved. I found that once you discover dynamic code generation is possible, you realise that it is used everywhere, for instance:
- Dapper (see this gist for ver1)
- Entity Framework (it enables lazy-loading when doing Code-First)
- protobuf-net
- Json.NET
- AutoMapper
- and many more!
BTW if you ever find yourself needing to dynamically emit IL code, I’d recommend using the Sigil library that was created by some of the developers at StackOverflow. It takes away a lot of the pain associated with writing and debugging IL.
However dynamically generated proxies will always run into the limitation that you can’t override non-virtual methods and they also can’t do anything with static methods or sealed class (i.e. classes that can’t be inherited).
.NET Profiling API and JITCompilationStarted() Method
How Typemock and JustMock achieve what they do is hinted at in a StackOverflow answer by a Typemock employee and is also discussed in this blog post. But they only talk about the solution, I wanted to actually write a small proof-of-concept myself, to see what is involved.
To start with the .NET profiling API is what makes this possible, but a word of warning, it is a C++ API and it requires you to write a COM component to be able to interact with it, you can’t work with it from C#. To get started I used the excellent profiler demo project from Shaun Wilde. If you want to learn more about the profiling API and in particular how you can use it to re-write methods, I really recommend looking at this code step-by-step and also reading the accompanying slides.
By using the profiling API and in particular the JITCompilationStarted method, we are able to modify the IL of any method being run by the CLR (user code or the .NET runtime), before the JITer compiles it to machine code and it is executed. This means that we can modify a method that originally looks like this:
public sealed class ClassToMock
{
public static int StaticMethodToMock()
{
Console.WriteLine("StaticMethodToMock called, returning 42");
return 42;
}
}
So that instead it does this:
public sealed class ClassToMock
{
public static int StaticMethodToMock()
{
// Inject the IL to do this instead!!
if (Mocked.ShouldMock("Profilier.ClassToMock.StaticMethodToMock"))
return Mocked.MockedMethod();
Console.WriteLine("StaticMethodToMock called, returning 42");
return 42;
}
}
For reference, the original IL looks like this:
IL_0000 ( 0) nop
IL_0001 ( 1) ldstr (70)00023F //"StaticMethodToMockWhatWeWantToDo called, returning 42"
IL_0006 ( 6) call (06)000006 //call Console.WriteLine(..)
IL_000B (11) nop
IL_000C (12) ldc.i4.s 2A //return 42;
IL_000E (14) stloc.0
IL_000F (15) br IL_0014
IL_0014 (20) ldloc.0
IL_0015 (21) ret
and after code injection, it ends up like this:
IL_0000 ( 0) ldstr (70)000135
IL_0005 ( 5) call (0A)00001B //call ShouldMock(string methodNameAndPath)
IL_000A (10) brfalse.s IL_0012
IL_000C (12) call (0A)00001C //call MockedMethod()
IL_0011 (17) ret
IL_0012 (18) nop
IL_0013 (19) ldstr (70)00023F //"StaticMethodToMockWhatWeWantToDo called, returning 42"
IL_0018 (24) call (06)000006 //call Console.WriteLine(..)
IL_001D (29) nop
IL_001E (30) ldc.i4.s 2A //return 42;
IL_0020 (32) stloc.0
IL_0021 (33) br IL_0026
IL_0026 (38) ldloc.0
IL_0027 (39) ret
And that is the basics of how you can modify any .NET method, it seems relatively simple when you know how! In my simple demo I just add in the relevant IL so that a mocked method is called instead, you can see the C++ code needed to achieve this here. Of course in reality it’s much more complicated, my simple demo only deals with a very simplistic scenario, a static method that returns an int
. The commercial products that do this are way more powerful and have to deal with all the issues that you can encounter when you are re-writing code at the IL level, for instance if you aren’t careful you get exceptions like this:
Running the demo code
If you want to run my demo, you need to open the solution file under step5_main_injected_method_object_array and set “ProfilerHost” as the “Start-up Project” (right-click on the project in VS) before you run. When you run it, you should see something like this:
You can see the C# code that controls the mocking below. At the moment the API in the demo is fairly limited, it only lets you turn mocking on/off and set the value that is returned from the mocked method.
static void Main(string[] args)
{
// Without mocking enabled (the default)
Console.WriteLine(new string('#', 90));
Console.WriteLine("Calling ClassToMock.StaticMethodToMock() (a static method in a sealed class)");
var result = ClassToMock.StaticMethodToMock();
Console.WriteLine("Result: " + result);
Console.WriteLine(new string('#', 90) + "n");
// With mocking enabled, doesn't call the static method, calls mocked version instead
Console.WriteLine(new string('#', 90));
Mocked.SetReturnValue = 1;
Console.WriteLine("Turning ON mocking of Profilier.ClassToMock.StaticMethodToMock");
Mocked.Configure("ProfilerTarget.ClassToMock.StaticMethodToMock", mockMethod: true);
Console.WriteLine("Calling ClassToMock.StaticMethodToMock() (a static method in a sealed class)");
result = ClassToMock.StaticMethodToMock();
Console.WriteLine("Result: " + result);
Console.WriteLine(new string('#', 90) + "n");
}
Other Uses for IL re-writing
Again once you learn about this mechanism, you realise that it is used in lots of places, for instance
- profilers, see this SO answer for more info (Ants and JetBrains)
- test coverage (NCover)
- productions monitoring systems