How to write better unit tests using RhinoMocks and mocking

In the last post we saw how the use of stub functionality of a mocking framework within a unit test. The aim of this post is build upon this and look at mocking functionality.

If you find yourself having to create a class to hold ‘state’, like this

using System.Collections.Generic;

namespace GettingStartedWithRhinoMocks
{
    public class TestLogger : ILogger
    {
        public TestLogger()
        {
            Messages = new List<string>();
        }

        public IList<string> Messages { get; private set; }

        public void LogError(string errorMessage)
        {
            Messages.Add(errorMessage);
        }
    }
}

And your test looks something like this

[Test]
public void Will_Write_To_Log_If_Customer_Fails_Validation_When_Adding_Customer_Using_Stub()
{
	// Arrange
	ICustomerValidator customerValidator = MockRepository.GenerateStub<icustomerValidator>();
	var testLogger = new TestLogger();

	var customerService = new CustomerService(customerValidator, testLogger);

	customerValidator.Stub(c => c.IsValid(null))
		.IgnoreArguments() // Use this because the argument is not important
		.Throw(new ArgumentException(CustomerValidator.CUSTOMER_NAME_ERROR_MESSAGE));  // This will simulate the throwing of an exception

	//Act
	customerService.AddCustomer(new Customer());

	//Assert
	Assert.That(testLogger.Messages.Count, Is.EqualTo(1));
}

You can eliminate the need for the TestLogger class by using a mock like this

[Test]
public void Will_Write_To_Log_If_Customer_Fails_Validation_When_Adding_Customer_Using_Stub_And_Mock()
{
	// Arrange
	ICustomerValidator customerValidator = MockRepository.GenerateStub<icustomerValidator>();
	ILogger logger = MockRepository.GenerateMock<ilogger>();

	var customerService = new CustomerService(customerValidator, logger);

	customerValidator.Stub(c => c.IsValid(null))
		  .IgnoreArguments() // Use this because the argument is not important
		  .Throw(new ArgumentException(CustomerValidator.CUSTOMER_NAME_ERROR_MESSAGE));  // This will simulate the throwing of an exception

	// Act
	customerService.AddCustomer(new Customer());

	// Assert
	logger.AssertWasCalled(l => l.LogError(CustomerValidator.CUSTOMER_NAME_ERROR_MESSAGE));
}

The difference is how we assert the exception was logged. The extension method ‘AssertWasCalled’ ensures that the ‘LogError’ method of a logger was called with the correct error message.

If we changed the assert to this

//Assert
logger.AssertWasCalled(l => l.LogError("Oh no"));

The test would fail and RhinoMocks would throw the following exception

Rhino.Mocks.Exceptions.ExpectationViolationException: ILogger.LogError("Oh no"); Expected #1, Actual #0.

So over the two posts we have refactored the unit tests from not using a mocking framework to using both stub and mock functionality of a mocking framework and still have unit tests which are easy to read and maintain.

Martin Fowlers’ article discusses the two styles testing. I recommend you choose which is better on a test by test basis.

The code is available at http://code.google.com/p/arrangeactassert/. The solution is in the GettingStartedWithRhinoMocks folder.

Comments