As JustMock is approaching its release date I decided to blog about it and more precisely about its dual nature. Probably you already know JustMock supports two (proxy and elevated) modes. That's why I am speaking about duality. In fact the duality in JustMock has many aspects. We could even say JustMock was built on two continents as one of the brains behind JustMock is working remotely on the project. So, duality is everywhere :) By the way, check out Mehfuz's blog, it is excellent place where you can see JustMock in action.

I've been thinking for some time about doing a series of posts about JustMock elevated mode and related topics. This is mainly because JustMock elevated mode uses an approach that is not usual in TDD and there is a lot of controversy. So let’s start :)

Firstly I would like to explain why there are two modes. When mocking a class or interface the usual approach is to subclass or implement the target and provide the functionality you desire. Roughly speaking this works well with virtual methods and properties only. One can imagine JustMock creates a new type using System.Reflection.Emit stuff and instantiates an object of it. There are situations (e.g. sealed classes, non-virtual methods, etc.) when this approach does not work. To solve this issue JustMock uses Profiling API to inject code at the beginning of the target method before it is compiled by JIT compiler. For more details see Rewrite MSIL Code on the Fly with the .NET Framework Profiling API article.

Remember the duality? JustMock comes with a weaver library that helps communicating with Profiling API. In matter of fact you get two products within a single bundle. Cool, isn't it? You may ask what you could use this weaver library for. Well JustMock weaver library is more of an isolation framework than a mocking framework. Although it was designed to facilitate JustMock’s mocking goals, the weaver is a small general purposes isolation framework. In essence the weaver library allows you to inject arbitrary code at the beginning/end of .NET methods and thus allows detouring of the program flow.

At the moment we do not provide documentation about JustMock weaver library. This is mainly because we are still discussing about how to do it in the right way. Sure we expect changes in weaver API but that should not stop us to play with it.

Now you know the reason why JustMock weaver library uses Profiling API let’s see how you can use it. Here is shown a small program that demonstrates some of JustMock weaver library features.

using System;
using Telerik.CodeWeaver.Hook;
   
namespace ConsoleApplication1
{
    sealed class MyClass
    {
        public void SayHello() { Console.WriteLine("Hello!"); }
   
        public void SayHello(string name) { Console.WriteLine("Hello {0}!", name); }
   
        public string RepeatHello(int count)
        {
            return (count < 1) ? null : string.Concat("Hello ", RepeatHello(count - 1));
        }
   
        public int ReturnZero() { return 0; }
    }
   
    class Program
    {
        static void Main(string[] args)
        {
            var c = new MyClass(); 
               
            Weaver<MyClass>.AtStartOfAction(_ => _.SayHello())
                .Inject(() => { Console.WriteLine("Hi!"); return false; });
   
            c.SayHello();
   
            Weaver<MyClass>.AtStartOfAction<string>((_, name) => _.SayHello(name))
                .Inject((name) => { Console.WriteLine("Hi {0}!", name); return "John".Equals(name); });
   
            c.SayHello("John");
            c.SayHello("Dave");
   
            Weaver<MyClass>.AtStartOfFunc<int, string>((_, count) => _.RepeatHello(count), FlowControl.ExecuteAndGo)
                .Inject((count) => { Console.WriteLine("RepeatHello({0})", count); return null; });
   
            Console.WriteLine(new MyClass().RepeatHello(3));
   
            Weaver<MyClass>.AtStartOfFunc<int>(_ => _.ReturnZero(), FlowControl.ExecuteAndStop)
                .Inject(() => 1);
   
            Console.WriteLine(new MyClass().ReturnZero());
        }
    }
}

For simplicity I chose to start with instance, non-virtual methods. I am going to explain line by line the program. Nowadays most (fluent) APIs have single point of entry and weaver API tries to follow this trend. The main entry point is Weaver<T> class. In our example MyClass is an instance type and that allows us to use it as a generic type parameter (we will see that for static types the syntax is slightly different).

So with

Weaver<MyClass>.AtStartOfAction(_ => _.SayHello())
                .Inject(() => { Console.WriteLine("Hi!"); return false; });

we are declaring that our target type is MyClass. Weaver<T> has a lot of static methods but for this tutorial we will focus on AtStartOfAction and AtStartOfFunc methods. We can classify methods in many ways: static vs. instance, virtual vs. non-virtual, etc. For current weaver API design we chose use static vs. instance and function vs action method classification (that might change in future). So we use:

  • AtStartOfAction for instance methods without return value
  • AtStartOfStaticAction for static methods without return value
  • AtStartOfFunc for instance methods with return value
  • AtStartOfStaticFunc for static methods with return value

The analogy is easy with System.Action and System.Func delegates. So with

AtStartOfAction(_ => _.SayHello())

we set a target method. AtStartOfAction accepts lambda expression that defines the target method. We use underscore as a variable (which is of type MyClass in this example) name for a good reason. The only purpose of this variable is to help us specify the target method. I want to have code that is visually as clean as possible. I could use XXX as a variable name as well but I find it much harder to read

Weaver<MyClass>.AtStartOfAction(XXX => XXX.SayHello())

compared to

Weaver<MyClass>.AtStartOfAction(_ => _.SayHello())

Finally we have to define injection that will be executed as a prologue for the target method.

Inject(() => { Console.WriteLine("Hi!"); return true /* skipOldMethod */; });

Let’s look close at the Inject method. In this case it has single parameter that is a delegate that accepts no parameters and returns boolean. The delegate signature resembles the signature of the target method SayHello but returns boolean instead of void. The return value is used as a flow control flag. If it is true then the body of the target method is not executed.

Let’s look at the statement as a whole.

Weaver<MyClass>.AtStartOfAction(_ => _.SayHello()).Inject(() => { Console.WriteLine("Hi!"); return true; });

With this we declare we want to execute Console.WriteLine("Hi!") instead of body of the target method MyClass.SayHello().

Let's look at the next statement

Weaver<MyClass>.AtStartOfAction<string>((_, name) => _.SayHello(name))
    .Inject((name) => { Console.WriteLine("Hi {0}!", name); return "John".Equals(name); });

This time we want to mock MyClass.SayHello(string name) method. Because the target method SayHello has a string parameter we have to use AtStartOfAction<string>. This is different from most frameworks. It is common to see 

xxx(obj.SayHello("")) or xxx(() => obj.SayHello(""))
 

just to satisfy the compiler but we choose to provide more explicit syntax. Suppose we have a target method

void TargetMethod(string name, int age, bool married)

In this case you should use

AtStartOfAction<string, int, bool>((_, name, age, married) => _.TargetMethod(name, age, married)

Our goal is to provide syntax that is as explicit as possible. We strongly encourage you to use the same parameter names in the lambda as used when you defined the target method. You could replace name with "123", age with -15 and so forth but then the code becomes less readable.

So, let’s continue with our example.

Weaver<MyClass>.AtStartOfAction<string>((_, name) => _.SayHello(name))
      .Inject((name) => { Console.WriteLine("Hi {0}!", name); return "John".Equals(name); });

Here we declare we want to inject Console.WriteLine("Hi {0}!", name) at the beginning of the target method. Please note that now the decision whether to execute the body of the target method is taken in our injection. We also can access the parameters of the target method invocation. This is a very powerful mechanism that you could employ in various scenarios.

So far we know how to do injections for methods that have no return value. Basically we define a delegate with the same method signature that returns boolean and we use the return value as a flow control flag.

Let’s see how we deal with methods that have return value. Obviously we can not reuse the same approach. For simple scenarios we have provided overloads of AtStartOfFunc method that accept flow control flag as a second parameter.

public enum FlowControl
{
    ExecuteAndStop = 0,
    ExecuteAndGo = 1,
}
 

Let’s have look at the next statement.

Weaver<MyClass>.AtStartOfFunc<int, string>((_, count) => _.RepeatHello(count), FlowControl.ExecuteAndGo)
      .Inject((count) => { Console.WriteLine("RepeatHello({0})", count); return null; });
 

The signature of the target method is

string RepeatHello(int count)
 

Using the analogy with System.Func delegate we use

AtStartOfFunc<int, string>((_, count) => _.RepeatHello(count), FlowControl.ExecuteAndGo)

 

The only difference with AtStartOfAction is the explicit flow control parameter. In current case as its value suggests we will execute the injection and then pass the control to the body of the target method. As the target method is recursive our injection will be executed at the beginning of each recursion entry. The result is shown on the screen shot below.

The last injection in our example has solely purpose to demonstrate how you can overwrite the return value of the target method.

Weaver<MyClass>.AtStartOfFunc<int>(_ => _.ReturnZero(), FlowControl.ExecuteAndStop).Inject(() => 1);

 

This time the flow control parameter has value FlowControl.ExecuteAndStop.

There is one thing I haven't talked about. Namely how to configure your environment to use JustMock profiler. In case you run this application from Visual Studio make sure you've enable JustMock from JustMock menu. In case you run this application from command line make sure you set up the following environment variables first.

SET COR_PROFILER={D1087F67-BEE8-4f53-B27A-4E01F64F3DA8}
SET COR_ENABLE_PROFILING=1

I hope you find this post interesting. Next time I will post about some more complex scenarios like injecting code at the end of the method, generic methods and ref/out parameters as well. Stay tuned.

 

Download example project


About the Author

Mehfuz Hossain

works as the Technical Advisor in DevTools Division . He is passionate playing around with the latest bits. He has been a Microsoft MVP, author of OS projects like LinqExtender and LINQ to flickr and most recently lighter (nodejs + mongo blogging engine). Prior to working at Telerik , Mehfuz worked as a core member in many high volume web applications including Pageflakes that is acquired by Live Universe in 2008. He is a frequent blogger and was also a contributor and site developer at dotnetslackers.com.

@mehfuzh

Related Posts

Comments