How to create your own Moq?

A realistic use-case for Dynamic Assemblies and MSIL

Oleksandr Redka
C# Programming

--

Why?

Today, the 9th of August 2023, I learned a pretty fun thing about the Moq framework.

Discussion fired up on Reddit and slowly flowed into this GitHub issue. Here is the quote from the author:

It seems that starting from version 4.20, SponsorLink is included. This is a closed-source project, provided as a dll with obfuscated code, which seems to at least scan local data (git config?) and sends the hashed email of the current developer to a cloud service. The scanning is provided as a .NET analyzer tool, which runs during the build. There is no option to disable this.
@GeorgDangl

This is a big no-no as an unauthorized collection of personal data can lead to significant consequences. See this (non-affiliated) link for more details.

Naturally, the majority of people are looking into migration away from Moq.
I’ve decided to go a different way and explore the technology behind similar projects and create my own Mocking Microframework. Let’s dive into it!

The Interface

As with any project, we have to define a high-level scope and features we wanna support. A few things from all Moq features are: mocking the interface, setting up a method call, and verifying that a given method was called a particular amount of times.

Sample structure of our class

And the usage of our new class with look like this:

Usage of MockMe class

But, how in the world you could implement anything like this?

Dynamic Assemblies and Dynamic Interface Implementation

The first question we have to ask is “How to create interface implementation in real-time?”

Fortunately, dotnet has pretty impressive reflection features and one of them is Dynamic Assemblies.
In a nutshell, this feature allows you to create Assemblies, Classes, and Methods in runtime and then define methods implementation using MSIL.

Step 1: Create Assembly and Module

Step 1 is more or less explicit: we have to define a dynamic assembly and create our service implementation in it. Let’s create AssemblyBuilder:

Definition of Assembly and Module

Our Assembly builder will be static and read-only, as we can keep all of our mocks in one place.

The next line may be confusing for fellow .Net engineers as you never see Modules in the IDE. But, in fact, all types are located in Modules and not in Assemblies. Assemblies are only a container for modules.

Anyway, we are ready to create our first type.

Step 2: Create type and Implement Interface

Type creation will happen at the same time we’ve created our mock instance.

Create type and implement the interface

After this step, we are done with simple things and we can proceed to the funniest part.
P.S. Magic with Guid.NewGuid in the type name is needed to guarantee unique names for types

Step 3: How to implement an interface?

Before we will proceed to MSIL generation, we need to know what exactly we want to generate. For this, let’s define our limitations and analyze how to walk our way around them.

The first thing is that our mock should have one and only one instance. This will guarantee that we will be able to execute Verify and get a reasonable response. This means, that once we will define our dynamic type, we have to create an instance of it and reject all modifications.

The second thing is that we need to gather information about method calls from the mock itself, so we should record those somehow.

To solve our first problem, we can pass a dictionary with the implementation of our methods to the constructor. So, we can change a dictionary from outside without making any changes to our class. The second problem would again require a dictionary and a separate interface to get information about the number of method calls. Here how our result should look like:

Fields and Constructor for our Mock

In a given example, IFancyService is the interface we want to implement and MockBase is the base class for all mocks. From my experience working with MSAL, I can say that it’s always a good idea to minimize the amount of things you want to do in the runtime. To achieve this, we will push some logic to our abstract class and limit ourselves to base-class calls whenever possible.

Now, let’s look into IFancyService.GetUsersCount implementation.

GetUsersCount implementation

The idea is that we will get an instance that represents the current method and check our implementations dictionary. After this check, we are getting a delegate instance, cast it to the appropriate type, and execute it. Otherwise, if the method was not explicitly implemented, we are returning the default value. Also, we have additional logic to count method calls.

Notice, that this implementation is almost independent from the actual method we are mocking. So, we can easily extract the majority of it into the base class.

MockBase class

And here is what our updated interface implementation should look like:

Updated implementation of Mock example

I’ve replaced a cast to delegate with dynamic invoke as it will be a bit easier to do with IL code. Also, I’ve replaced GetCurrentMethod with getting an actual interface method because of a small IL trick I’ll show you later.

We have to change our MockMe constructor to inherit our MockBase class with our freshly created type builder.

Now, we are ready to play with MSIL emission.

Step 4: Create a constructor

As we’ve discussed, we have a base class MockBase that contains some of the implementation and a constructor with parameters. The first step for us will be to create a constructor for our dynamic type.

Here is what we want to have in the end:

Example of the constructor

And here is how we are gonna achieve that:

Create a constructor and call to Base.

Here we are resolving a constructor for our base type. To avoid having to rewrite this method later, we will copy all of the parameters our constructor has and declare a constructor with the same signature in our mock type. Then, we are getting фт IL generator that represents the constructor’s body and start emitting the first instructions. Let’s see what they are doing.

  • Ldarg — takes an argument with a given number and puts it on the call stack
  • Call — here we will actually call our base constructor.
  • Ret — returns from method.

So, we are loading all constructor parameters onto our stack and then we are performing the call to a base class’ constructor. And we are done!

Step 5: Analyze the given interface and emit IL

Here, we will have to do smth similar. We need to iterate all interface methods and create an implementation for each of them.

Iterate all interface methods.

In order for us to get inspiration for the IL, let’s take a look at a decompiled MockExample class and let’s see what we have there.

Reminder of GetUsersCount() implementation
IL code of GetUsersCount()

Decompiler already split the whole body into neat lines according to our source code lines. Let’s analyze.

The first set of instructions is getting the given interface method and loading it into a local variable. Let’s review the new opcodes:

  • Ldtoken — provides us with a runtime handle of the given metadata token: type or method.
  • Ldstr — puts a constant string on top of a stack.
  • Stloc.0 — puts the current top of the stack value to local variable #0.

GetTypeFromHandle allows us to get an instance of a Type object from a given runtime handle. The same could be done for Methods.

And, here is the catch I’ve promised before. We can skip a call to GetMethod() as at the time we are emitting IL we already have a reference to MethodBase. So, this is what our implementation will look like:

Emit getting interface method.

We are taking the runtime handle of a method we already know (it will be key in our dictionary). Then we are getting MethodBase for this handle. And the result is stored in local variable #0.
The next section is an actual call to base.IsImplemented with our current method.

Section 2

New opcodes:

  • Ldloca.s — loads a pointer to reference a variable from a given local variable. S stands for the shortened version of addressing local variables.
  • Brfalse.s — jumps to a given label if the value on top of the stack is false, null, or zero.

And here is our emission code:

Emit call to base.IsImplemented

Notice, that we have to declare a label that will be used to define what will happen if we don’t have any implementations. To recap: here we are loading an instance of a current class (which is always the 0th argument of any instance method) then we are loading our interface method instance and the last one is the out parameter for Delegate. Then we are actually calling our base method. This call will put a boolean value on top of our stack, and if the method is not implemented, we will jump to our label.

Let’s move on to the next part — building DynamicInvoke() call.

Dynamic invoke call.

In our example, implementation is pretty straightforward, as we are not forwarding any parameters to that method. So we can copy it as-is.

DynamicInvoke call

New opcodes are:

  • Callvirt — calls a virtual method using virtual methods table. This is required to work with methods that could be overridden. Also, callvirt will guarantee that the instance of the class you are working with is not null. If you ever had a NullReferenceException — this is one of the operations that could throw it.
  • Unbox.any — allows to cast an instance of object to a given type. It will handle reference and value types differently.

I will not describe what will happen here. Try to make sense of it yourself.

The last part is handling the default value return.

Return default value

In our example, it’s just a default integer but we wanna build something more flexible to return a default value of any type we need.

For this, we have to create an empty local variable of the given type, initialize it with a default value and then return it.

Return default value

Also, we have to mark our label used in the brfalse.s above. And here are the new opcode:

  • Initobj — initializes a reference to an object from the top of the stack with an appropriate default value.

This was our last step to mock all of the given interface methods. Few things left — we have to implement the Instance property, Verify, and Setup methods.

Step 6: Make MockMe<> class usable

To make usage of the Setup method possible, we have to create an external dictionary of implementations and pass it as a constructor parameter to our dynamic class. Here is what the updated MockMe<> constructor looks like:

MockMe<> constructor creates a new instance of the type.

Now, we can just update the ‘implementations’ dictionary to set up our mock.

Expression Trees magic to configure implementations.

I will not dive deep into expression trees or this article will take too much of the time. In a few words — this method will get the actual method you want to mock and put it into the implementations dictionary.

‘Verify’ method is used to ensure certain calls count.

Some more expression tree magic and then the cast of our instance to MockBase so we could get the number of specific method calls.

Aaaand that’s it! Our DIY Mocking framework has its first feature and it’s ready to rock! Check out the repository to see the full source code covered with simple uint-tests.

6. Disclaimer and conclusion

Please, don’t do it on your project right after reading this article.

Your team-lead when you want to add some cool .net hacks to the project.

Reflection and dynamic assemblies carry insane maintenance penalties and potential security risks.

The main goal behind this article is to get an overview of the features available in dotnet as well as to provide a high-level idea of the mocking frameworks hidden details.

I’m looking forward to any feedback!

P.S. This is how Dall-E draws “First OpCode” Achievement:

AI-generated congratulations

References and Links

Dynamic programming in .Net
https://learn.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/

OpCodes
https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes?view=net-7.0

Source Code
https://github.com/Lex45x/Medium.Examples/tree/main/MockingWithRespect

--

--