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:
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.
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.