TypeMock uses the Profiler API to allow mocking, stubbing, etc. of classes used by code under test. It has the ability to handle sealed classes, static classes, non-virtual methods, and other troublesome-yet-oft-encountered scenarios in the world of unit testing. Other frameworks rely on proxies to intercept method calls, limiting them to be able to only fake virtual, abstract, and interface members. They also rely on dependecy injection to place the proxies as the concrete implementation of calls to the abstracted interface members.
Anyone working with a legacy codebase is bound to run into static method calls (especially in the data access layer), dependencies on concrete types with non-virtual methods, and sealed class dependencies (HttpContext anyone?). The only way to unit test this without refactoring is with TypeMock. I've never used TypeMock, and I'm sure it's a great product, but it's not free. I decided to spike some code to see if I could solve the problem of mocking static method calls through AOP with PostSharp. What I came up with was a pretty ugly solution with limited applicability that will probably help no one. Let's take a look!
The example codebase is trivial: we have a PersonGetCommand representing the business logic we want to test and a PersonDao class with a static GetPerson method representing a method we want to stub. GetPerson returns, you guessed it, a Person.
To intercept calls to the static method, we're going to place a MockableAttribute on the method. It looks like this:
The code for the attribute talks to our 'mock framework'. It looks like this:
To prove that the example codebase works correctly, we have this test:
And this test shows our MockableAttribute (as well as our makeshift mock framework) in action:
Note that I chose a Rhino-like syntax for the expecations. Also note that we aren't really 'mocking' the call because we aren't verifying expectations or arguments. This could easily be done, however.
This is all dreadfully simple thanks to PostSharp. But is this approach ready for primetime? It's far from it, in fact, and here are the reasons:
Some of these concerns can be alleviated, and some cannot. I would like to prove this out soon (though I doubt I'll be able to):
If anyone ever reads this post and is interested, I will find a way to provide the solution I used to spike this.
(Side thought: I wonder, if I were to start an open source project called 'SharpMock' as an alternative TypeMock, could I get a free version of TypeMock to aid in its development?)
Anyone working with a legacy codebase is bound to run into static method calls (especially in the data access layer), dependencies on concrete types with non-virtual methods, and sealed class dependencies (HttpContext anyone?). The only way to unit test this without refactoring is with TypeMock. I've never used TypeMock, and I'm sure it's a great product, but it's not free. I decided to spike some code to see if I could solve the problem of mocking static method calls through AOP with PostSharp. What I came up with was a pretty ugly solution with limited applicability that will probably help no one. Let's take a look!
The example codebase is trivial: we have a PersonGetCommand representing the business logic we want to test and a PersonDao class with a static GetPerson method representing a method we want to stub. GetPerson returns, you guessed it, a Person.
To intercept calls to the static method, we're going to place a MockableAttribute on the method. It looks like this:
[Mockable]
public static Person GetPerson(int personId)
{
return new Person { Id = personId, Name = String.Format("Person {0}", personId) };
}
The code for the attribute talks to our 'mock framework'. It looks like this:
[Serializable]
public class MockableAttribute : OnMethodInvocationAspect
{
public override void OnInvocation(MethodInvocationEventArgs eventArgs)
{
MethodInfo invokedMethod = eventArgs.Delegate.Method;
// If this gets called during recording, we have to register the expectations
if (Expect.IsInRecordMode)
{
Expect.RegisterExpectedCall(invokedMethod);
Expect.RegisterExpectedArguments(invokedMethod, eventArgs.GetArgumentArray());
Expect.RegisterExpectedReturnValue(invokedMethod, Expect.CurrentlyExpectedReturnValue);
}
// If not, we need to check to see if we're mocking the method
else
{
// If it is mocked, we intercept the call
if (Expect.MethodIsMocked(invokedMethod))
{
eventArgs.ReturnValue = Expect.GetExpectedReturnValue(invokedMethod);
}
// Otherwise, we invoke the method
else
{
eventArgs.ReturnValue = eventArgs.Delegate.Method.Invoke(eventArgs.Delegate.Target, eventArgs.GetArgumentArray());
}
}
}
}
To prove that the example codebase works correctly, we have this test:
[Test]
public void Will_Get_Person()
{
var expectedName = "Person 23";
var personId = 23;
var commandUnderTest = new PersonGetCommand { PersonId = personId };
var resultingPerson = commandUnderTest.Execute();
Assert.That(resultingPerson.Name == expectedName);
Assert.That(resultingPerson.Id == personId);
}
And this test shows our MockableAttribute (as well as our makeshift mock framework) in action:
[Test]
public void Will_Get_Person_From_Mocked_Call()
{
var ryan = new Person { Id = 11, Name = "Ryan" };
// Set the expectation for the dao call
Expect.Call(delegate { PersonDao.GetPerson(0); }, ryan);
// Note that we don't care about the person id argument
var commandUnderTest = new PersonGetCommand();
var resultingPerson = commandUnderTest.Execute();
Assert.That(resultingPerson == ryan);
}
Note that I chose a Rhino-like syntax for the expecations. Also note that we aren't really 'mocking' the call because we aren't verifying expectations or arguments. This could easily be done, however.
This is all dreadfully simple thanks to PostSharp. But is this approach ready for primetime? It's far from it, in fact, and here are the reasons:
- It only works for classes you control (and currently only for static methods). So if you're making a call to a static method in a third-party dll or a .NET framework class, this is useless.
- Requires the use of an attribute. It may only be metadata, but testing concerns shouldn't be leaking into application source.
- It modifies that actual assemblies being tested. Technically, this approach tests only a modified-for-test assembly, not the assembly that we want to release into production.
- The assembly under test requires a reference to PostSharp.Public.dll and PostSharp.Laos.dll.
- Performance could be poor. Code weaving adds overhead to the compilation process. Tests and the code they test are frequently compiled.
PostSharp is tied to MSBuild. NAnt tasks running on a build server might not be able to execute tests using this mock framework.(See comments to this post from PostSharp creator Gael Fraiteur.)- A bunch of other things I forgot.
Some of these concerns can be alleviated, and some cannot. I would like to prove this out soon (though I doubt I'll be able to):
- Interception can be done at the call site without modifying the called assembly.
- The code weaver can be provided with interception points (join points? advice points? I don't understand AOP terminology at all) without them being marked with attributes. This has to be true if we want to do (1) anyway. Unfortunately, it's not going to be easy (otherwise Laos would probably already do it).
- Nothing can be done about modifying the assemblies being tested; that's the whole point. We're weaving code to achieve interception.
Nothing can be done about referencing PostSharp. Again, that's the point.Gael corrected me on this. We'll tackle this in the distant future.- Overhead per-compilation isn't too bad. It's better than overhead per-test.
- There are ways to invoke PostSharp from the command line
, so NAnt tasks the coupling to MSBuild can be overcome(Deleted not only for being obvious typo nonsense but also because Gael points out that there is already a NAnt task for PS 1.5). I don't really care about this right now (still true anyway). - I sometimes remember things I previously forgot.
If anyone ever reads this post and is interested, I will find a way to provide the solution I used to spike this.
(Side thought: I wonder, if I were to start an open source project called 'SharpMock' as an alternative TypeMock, could I get a free version of TypeMock to aid in its development?)
Comments
This is a good use of PostSharp! hope you'll have the force to complete the project!
Some precisions:
1. PostSharp is not bound to MSBuild. There is a command-line utility (from version 1.0) and an NAnt task (from version 1.5).
2. PostSharp Laos is less bound to custom attributes than you may think. You you design a DSL or XML file, you can 'easily' read it from inside PostSharp. See CompoundAspect for high-level use, or ICustomAttributeProvider for low-level.
3. Depending on your time budget (hours or days/weeks), you can develop a solution that does not require PostSharp to be referenced to your assemblies. But you should use the low-level APIes for that... and study MSIL. It's not as easy as with PostSharp Laos, but you are virtually unlimited.
You can ask questions on http://www.postsharp.org/forum if you have any question.
Good luck!
-gael
This should be fixed now.
@Gael
Thanks for the feedback! I will actually update this post soon per your comments. Also, you can expect to hear from me soon on your excellent support forums :)
Before starting the open source project have a look at this patent by TypeMock http://www.wipo.int/pctdb/en/wo.jsp?WO=2008038265
If the url doesn't show google typemock patent office.
Basically they locked the concept of isolating code for testing purposes. That's why the crazy prices.
I was about to start an open source project just like you but then I just realized that there is no way around it. Maybe I misunderstood the patent text so I might be wrong. Somebody should clarify that.
Patents are pretty bad for evolution in general.
Cheers,
Cosmin
Thanks for the heads up. I might look into this more later, but for now I'm going to simply ignore it... I'm stating that plainly here for everyone to see. I will deal with this in more detail if it becomes a problem.
Intellectual property laws in general are bad for progress.
Interesting use of PostSharp for this. I'd like you to stay on course, and see where it goes!
So if you'd like a Typemock Isolator license, feel free to contact me at gilz at typemock dot com. We give free licenses for open source projects. And you won't be violating any patent, I assure you. You can download the open source version (fully featured) here.
And do let me know when and how you continue. I'm interested in driving innovation more than protecting IP.
Gil Zilberfeld
Typemock