Using Lambdas for return values in Rhino.Mocks
- by PSteele
In a recent StackOverflow question, someone showed some sample code they’d like to be able to use. The particular syntax they used isn’t supported by Rhino.Mocks, but it was an interesting idea that I thought could be easily implemented with an extension method. Background When stubbing a method return value, Rhino.Mocks supports the following syntax: dependency.Stub(s => s.GetSomething()).Return(new Order());
The method signature is generic and therefore you get compile-time type checking that the object you’re returning matches the return value defined by the “GetSomething” method.
You could also have Rhino.Mocks execute arbitrary code using the “Do” method:
dependency.Stub(s => s.GetSomething()).Do((Func<Order>) (() => new Order()));
This requires the cast though. It works, but isn’t as clean as the original poster wanted. They showed a simple example of something they’d like to see:
dependency.Stub(s => s.GetSomething()).Return(() => new Order());
Very clean, simple and no casting required. While Rhino.Mocks doesn’t support this syntax, it’s easy to add it via an extension method.
The Rhino.Mocks “Stub” method returns an IMethodOptions<T>. We just need to accept a Func<T> and use that as the return value. At first, this would seem straightforward:
public static IMethodOptions<T> Return<T>(this IMethodOptions<T> opts, Func<T> factory)
{
opts.Return(factory());
return opts;
}
And this would work and would provide the syntax the user was looking for. But the problem with this is that you loose the late-bound semantics of a lambda. The Func<T> is executed immediately and stored as the return value. At the point you’re setting up your mocks and stubs (the “Arrange” part of “Arrange, Act, Assert”), you may not want the lambda executing – you probably want it delayed until the method is actually executed and Rhino.Mocks plugs in your return value.
So let’s make a few small tweaks:
public static IMethodOptions<T> Return<T>(this IMethodOptions<T> opts, Func<T> factory)
{
opts.Return(default(T)); // required for Rhino.Mocks on non-void methods
opts.WhenCalled(mi => mi.ReturnValue = factory());
return opts;
}
As you can see, we still need to set up some kind of return value or Rhino.Mocks will complain as soon as it intercepts a call to our stubbed method. We use the “WhenCalled” method to set the return value equal to the execution of our lambda. This gives us the delayed execution we’re looking for and a nice syntax for lambda-based return values in Rhino.Mocks.
Technorati Tags: .NET,Rhino.Mocks,Mocking,Extension Methods