Rhino.Mocks, Lambdas and Expectations
An interesting post on the Rhino.Mocks users list that I thought I’d share.
Background
Let’s start with a simple interface for an item:
public interface IItem
{
void DoFoo();
void DoBar();
}
Now let’s say we have a custom collection of these IItem objects. In this collection, we have a “Process” method and we’d like that process method to determine which method to call on each IItem – either DoFoo() or DoBar(). We’ll accomplish this with an Action<IItem> delegate:
public interface IItemCollection
{
void Add(IItem item);
void Remove(IItem item);
void Process(Action<IItem> action);
}
Now we have a service that works with that collection:
public class SomeService
{
private IItemCollection items;
public SomeService(IItemCollection items)
{
this.items = items;
}
public void Foo()
{
items.Process(i => i.DoFoo());
}
public void Bar()
{
items.Process(i => i.DoBar());
}
}
So now, we want to write a unit test for SomeService to make sure that the proper method (DoFoo/DoBar) is called depending on which service method we call (Foo/Bar).
Unit Test
[TestMethod]
public void TestMethod1()
{
// arrange
var items = MockRepository.GenerateMock<IItemCollection>();
var service = new SomeService(items);
// act
service.Foo();
// assert
items.AssertWasCalled(c => c.Process(i => i.DoFoo()));
}
This all seems pretty simple (it is – it’s just a sample). But if you run the test, it fails. Why?
Explanation
The answer lies in some of the plumbing that lambdas do for us. Remember that lambdas are just shortcuts for anonymous delegates. And anonymous delegates are shortcuts for writing a totally separate method. If we “reverse engineer” how this code looks to the compiler, we’ll see something like this for SomeService.Foo():
public void Foo()
{
items.Process(OnAction);
}
private void OnAction(IItem i)
{
i.DoFoo();
}
See how the actual call being made is to a private method inside the SomeService class. If we look at our unit test (which also uses a lambda as part of the expectation), we’ll see something similar:
[TestMethod]
public void TestMethod1()
{
...
// assert
items.AssertWasCalled(c => c.Process(OnAction));
}
private void OnAction(IItem i)
{
i.DoFoo();
}
Again, another private method inside the unit test assembly. So we’ve told Rhino.Mocks to make an assertion that a call was made to IItemCollection.Process and a delegate to the private OnAction within the test class was called. In reality, the delegate passed to IItemCollection.Process was to the private method within the SomeService class. Therefore, Rhino.Mocks reports a failure.
It’s important to understand how lamdas and anonymous methods work. They make our job as software developers a whole lot easier, but there are some things to watch out for.