Monday, October 12, 2009

T4 Mocks

Test-driven-development is a gateway drug to some pretty hard-core code, such as mocking frameworks.  If you’re not familiar with the concept of mocking frameworks, they allow you to create fake implementations of interfaces that you can use when testing.  This means you can unit test code that typically would require a database, file access, network IO, etc. without actually having to have any of those things. Honestly, I’m still learning about the proper use of mocking frameworks, but one thing I’ve learned is that each have their own syntax, which can sometimes be a little intimidating to newcomers:

var mocks = new MockRepository();
var customerServiceMock = mocks.DynamicMock<ICustomerService>();
var testCustomers = new[]{new Customer(), new Customer(), new Customer()};
Expect.Call(customerServiceMock.GetAll).Return(testCustomers.AsQueryable());            
mocks.ReplayAll();
 
var target = new CustomerController(customerServiceMock);
 
var result = target.Index(1);
 
mocks.VerifyAll();

Now to be fair, this isn’t unreasonably complicated code.  We’re saying “I don’t feel like making a _real_ implementation of ICustomerService, so make one for me at runtime. Expect GetAll to be called, and when it is, return testCustomers.” As a “DynamicMock”, any other calls to methods on customerServiceMock will be ignored or return null. There’s an odd ‘ReplayAll/VerifyAll’, which tells the mocking framework “OK, we’re done setting up the mock, get ready…” and then “Ok, verify everything we expected just happened.  With this in place, we can test the method without the overhead of a database, and isolate the piece being tested. This works well, and once you learn the syntax, is fairly straight-forward.  Other frameworks, such as Moq, have some syntax sugar that makes mocking even more straight forward, but in general, they all work by creating the class at runtime and exposing some methods that you use to set up the mock and verify the code under test.  Like I said, I can’t really find much wrong with this, except that it felt a little wordy. It was mostly out of curiosity that I decided to spike creating my own mocking framework, with a slight twist.  Instead of generating the classes at runtime, what if we used code-generation to generate them at compile-time?  We could look at our project for interfaces to be mocked, and create our own implementations that enabled functionality useful at test-time.   The end result would be more intellisense-friendly, and (I think) straight-forward. This would enable code like this:

var mock = new MockCustomerRepository();
mock.GetAllMethod.Returns(new Customer(), new Customer(), new Customer(), new Customer());
 
var target = new CustomerController(mock);
var actual = target.Index();
 
mock.GetAllMethod.MustBeCalled();

So, I wrote some T4 based on Oleg Sych’s samples.  It uses Microsoft’s Introspection engine to peek at an assembly and find interfaces to be mocked.  For each interface, it generates a class named “Mock[InterfaceName]”, with all of the interface implemented (actually, it currently only does the methods, leaving out events and properties).  In addition, for each method, it generates a corresponding property named [MethodName]Method.  This property enables the mocking functionality via a secret sauce of generics and extension methods.  Say there is a method like this on the interface:

Customer GetCustomerById(int id)

The T4 generated Mock looks like this:

Customer GetCustomerById(int id){…}
public class GetCustomerByIdParameters {public int id {get;set;}}
MockMethodInfo<GetCustomerByIdParameters, Customer> GetCustomerByIdMethod {get;set;}

Which means inside the test, you can do this:

mock.GetCustomerByIdMethod.Returns(new Customer()});

It also allows for lambdas, so for more complex tests, you can do things like:

mock.GetCustomerByIdMethod.Return(p=>return new Customer(){Id=p.id});

In addition, extension methods on MockMethodInfo enable checking that calls were made:

mock.GetCustomerByIdMethod.MustBeCalled().WithParameters(p=>p.id==1);

That’s about all it does right now.  There are a few things I like about this over ‘traditional’ mocking frameworks.  First, the generated code is very discoverable via intellisense.  If a developer can get to “new MockMyService()”, he can probably fall into the rest fairly quickly.  Second, the syntax (I think) is slightly more readable and concise.  As with previous code-gen experiments, this is more of a learning exercise than actual production-ready project, but if you’re interested, you can download the code and let me know what you think!

No comments: