The problem
I ran into a snag testing some code today with Rhino Mocks. I was mocking calls to a repository and inspecting the repository method calls and arguments passed.
Everything started off pretty normal with the repository dependency injected into the class under test:
// Arrange
var repository = MockRepository.GenerateMock<ICustomerRepository>();
var passenger = new Passenger(repository);
// Act
passenger.CallTheMethodUnderTest(arg1, arg2);
In my Assert section, I needed to check that the correct repository methods were called with the correct arguments. The catch was, one of the arguments was a class created within the method under test. I thought I could create and assert the equivalent Customer in my test like this:
// Assert
var expectedCustomer = new Customer
{
CustomerID = "1234",
FirstName = "Joe",
LastName = "Wilson"
};
repository.AssertWasCalled(x => x.SomeRepositoryMethod(
arg1,
arg2,
expectedCustomer));
The two Customer objects (in the test and in the method under test) had the same values, but of course, were not the same objects. They were two different Customer objects that happened to have the same values. So Rhino Mocks told me that my expected Assert wasn't met.
The answer
I knew Rhino Mocks could ignore arguments with
Args<T>.Is.Anything
But I knew what the values should be. In fact, for this test, the values being passed into the repository calls were just as important as the calls themselves.
So I tried comparing the values of the two Customer objects instead of the objects themselves:
repository.AssertWasCalled(x => x.SomeRepositoryMethod(
arg1,
arg2,
Arg<Customer>.Matches(c =>
c.CustomerID.Equals("1234") &&
c.FirstName.Equals("Joe") &&
c.LastName.Equals("Wilson"))));
So close! But now Rhino Mocks told me if I used Arg for one parameter, I'd better use it for all of them. No biggie. I made the change and this worked:
repository.AssertWasCalled(x => x.SomeRepositoryMethod(
Arg<int>.Is.Equal(arg1),
Arg<int>.Is.Equal(arg2),
Arg<Customer>.Matches(c =>
c.CustomerID.Equals("1234") &&
c.FirstName.Equals("Joe") &&
c.LastName.Equals("Wilson"))));
Postmortem
The Arg statement let's you specify a type and tell Rhino Mocks you don't know or don't care about the value:
Args<int>.Is.Anything
You can also tell Rhino Mocks you know the type and what the value should be:
Args<int>.Is.Equal(7)
If you have a non-primitive type or complex assert, you can also inspect values with the Matches statement:
Arg<Customer>.Matches(c =>
c.CustomerID.Equals("1234") &&
c.FirstName.Equals("Joe") &&
c.LastName.Equals("Wilson"))));
I realize the code in these tests is probably way too familiar with the code under test. I prefer testing state over this kind of interaction testing where possible. But this was a case where the code under test had a void return and didn't do much besides parse some values and call repository methods based on those values.
In times like this, I'm glad I have interaction testing to fall back on so I can verify my code is behaving the way it should.