Kash Farooq's software development blog

.NET Developer

Checking what was sent to a mock, readability of test code and readability of assert messages

Posted by Kash Farooq on August 7, 2009

There are several ways to determine what was sent to your mock object.
In this blog post I’ll look at using Rhino Mocks and Moq to test the same piece of code and check that the correct data was sent to a dependency. I’ll then also look at not bothering with a Mocking Framework, and just using a Fake instead.

If the method being called on the mock receives a simple type, it is easy to check that the correct data was sent to the mock. For example, with Rhino Mocks:

dependency.AssertWasCalled(m=>m.MyMethod(5));

However, things aren’t as simple if MyMethod receives a complex type.

For the examples below, here is the code under test. I have a dependency that receives a complex type:

//A complex type
public class PersonDetail {
   public string Name { get; set; }
   public string Address { get; set; }
}

//The dependency
public interface IPersonDetailsRepository {
   void StorePersonDetails(PersonDetail personDetail);
}

public class SystemUnderTest
{
    private readonly IPersonDetailsRepository repository;
    public SystemUnderTest(IPersonDetailsRepository repository) {
        this.repository = repository;
    }
}

public void CreatePerson(string name, string address) {
   //intentional bug (address and name the wrong way around) so that we can check the test failure messages
   repository.StorePersonDetails(new PersonDetail {Name = address, Address = name});
}

Rhino Mocks Matches syntax

With Rhino Mocks, one way to check the parameter sent to StorePersonDetails is to use the Matches syntax.

[Test]
public void CheckPersonDetailsAreSaved_RhinoMocks_ArgsMatches()
{
    const string personAddress = "London";
    const string personName = "John Smith";

    var repository = MockRepository.GenerateStub<IPersonDetailsRepository>();
    new SystemUnderTest(repository).CreatePerson(personName, personAddress);

    repository.AssertWasCalled(x => x.StorePersonDetails
                                     (Arg<PersonDetail>.Matches(person => person.Name == personName)));

    //Error:
    //Rhino.Mocks.Exceptions.ExpectationViolationException:
    //IPersonDetailsRepository.StorePersonDetails(y => (y.Name = "John Smith")); Expected #1, Actual #0.
}

So, we caught the bug. However, I’d argue that it is not at all clear why the test failed. You’d have to look at the code under test. You cannot work out what went wrong by just looking at the error message. Is the error message telling us the dependency was not called at all, or that the argument sent was incorrect?

Rhino Mocks GetArgumentsForCallsMadeOn

The next Rhino Mocks method I’ll look at is one that I’ve discussed before. I think GetArgumentsForCallsMadeOn gives you a much clearer way to understand why your test failed:

[Test]
public void CheckPersonDetailsAreSaved_RhinoMocks_GetArgumentsForCallsMadeOn() {
    const string personAddress = "London";
    const string personName = "John Smith";

    var repository = MockRepository.GenerateStub<IPersonDetailsRepository>();
    new SystemUnderTest(repository).CreatePerson(personName,personAddress);

    var objectSentToRepository = (PersonDetail)repository
                                                  .GetArgumentsForCallsMadeOn
                                                       (r => r.StorePersonDetails(null))[0][0];
    Assert.That(objectSentToRepository.Name,Is.EqualTo(personName));

    //Error
    //NUnit.Framework.AssertionException:   Expected string length 10 but was 6. Strings differ at index 0.
    //Expected: "John Smith"
    //But was:  "London"
}

The error message you see in your test runner is clear – you know exactly what the bug in the code is. Much clearer than using the Matches syntax. However, you need an explicit cast and you are tying the test to the implementation in terms of parameter order of the dependency (see the caution at the bottom of my post about GetArgumentsForCallsMadeOn).

Moq

With Moq you also get an unclear assert error message:

[Test]
public void CheckPersonDetailsAreSaved_Moq() {
    const string personAddress = "London";
    const string personName = "John Smith";
    var repository = new Mock<IPersonDetailsRepository>();

    new SystemUnderTest(repository.Object).CreatePerson(personName, personAddress);

    //Both these give the same error seen below.
    repository.Verify(r=>r.StorePersonDetails(Match<PersonDetail>.Create(person => person.Name == personName)));
    repository.Verify(r=>r.StorePersonDetails(It.Is<PersonDetail>(x=>x.Name==personName)));

   //Error:
   //Moq.MockException:
   //Invocation was not performed on the mock:
   //x => x.StorePersonDetails(Match`1.Create(x1 => (x1.Name = "John Smith")))
}

Again, is the error message telling us the dependency was not called at all?

Note: you can also use callbacks to get a cleared test failure message. See: Using Moq callbacks to check complex arguments sent to a dependency.

Fakes

There is another way. If your dependency interface is small, why not use a Fake object? – i.e. don’t use a Mocking Framework at all:

private const string personAddress = "London";
private const string personName = "John Smith";

private class FakeRepository : IPersonDetailsRepository {
    public PersonDetail CollectedPersonDetails;
    public void StorePersonDetails(PersonDetail personDetail) {
        CollectedPersonDetails = personDetail;
    }
}

[Test]
public void CheckPersonDetailsAreSaved_Moq() {
    var fakeRepository = new FakeRepository();
    new SystemUnderTest(fakeRepository).CreatePerson(personName, personAddress);

    Assert.That(fakeRepository.CollectedPersonDetails.Name, Is.EqualTo(personName));

    //Error:
    //NUnit.Framework.AssertionException:   Expected string length 10 but was 6. Strings differ at index 0.
    //Expected: "John Smith"
    //But was:  "London"
}

Using a fake, we have a simple, readable test and when there is an assert failure, it is obvious what the bug is.

Advertisements

One Response to “Checking what was sent to a mock, readability of test code and readability of assert messages”

  1. Alex said

    How would you get a meaningful error message if you just wanted to assert that a method was/was not called but were not bothered about the arguments?

    I can do this to manually set a message:

    object.AssertWasCalled(x => x.MyMethod(Arg.Is.Anything),
    options => options.Message ("Method should have been called"));

    but this seems messy.

Sorry, the comment form is closed at this time.

 
%d bloggers like this: