Test Doubles and RhinoMocks

(Originally written in 2009)
  1. What are Test Doubles?

    Test Doubles pretend to be other objects. The nice thing about them, unlike children, is that we can tell them exactly how to behave.


    graphics
  2. But First, Two Important Terms
    1. System Under Test (SUT)

      This is the software object we are interested in testing.


      graphics
    2. Depended-On Component (DOC)

      Support objects that the SUT needs to do its job. Often the DOC is queried by the SUT for information, or the SUT may give information to the DOC, like in the case of logging.

    [A lot of electrons have been spilt arguing what is a "mock" and what is a "stub". Martin Fowler has a good article entitled Mocks Aren't Stubs. In general if your Depended-On Component (DOC) is just being queried by the SUT to return values, it's a "stub" object. If you do an Assert on your DOC to see how it was used, it's a "mock". You can create a "Mock" object in Rhinomocks, but use it as a stub. Rhinomocks has three different APIs which have grown with time. I will not make much of a distinction on this page and leave it to wiser people to sort out the details of nomenclature like Gerard Meszaros's Test Doubles.]

    rhinos
  3. With life already being so complicated, why on earth would you ever want to use a Test Double object?

    Making calls to databases or other external entities is not desirable in unit testing because it slows down the tests and it assumes the external entities are in a specific state. To get around this problem we use Test Doubles. I'll be using the RhinoMocks framework although many other frameworks exists like moq.

  4. Stubbing an external entity like a database

    How do we stub out a database call? Here we stub the database access in the CustomerRepository class. We pass the ICustomerRepository in the constructor, (although this could be done with something like StructureMap, a dependency injection framework).

    bank

    In this example, we own a very simple, but secretive Swiss bank. Customers are known only by their secret name or number. We calculate interest daily. Customer information is kept in a database, but for testing purposes we don't want to go to a real database to test our interest calculations, we need a stub for the database.

    The test is shown using the older Record/Playback mock syntax, and the newer, cleaner, ArrangeActAssert (AAA) method with a stub.

    using System;
    using NUnit.Framework;
    using Rhino.Mocks;
    
    namespace SimpleBank0 {
        public class Customer {
            private readonly string SecretName;
            public readonly decimal Amount;
            public Customer(string secretName, decimal amount) {
                SecretName = secretName;
                Amount = amount;
            }
        }
        public class InterestCalculator {
            readonly ICustomerRepository customerRepository;
            public InterestCalculator(ICustomerRepository customerRepository) {
                this.customerRepository = customerRepository;
            }
            public decimal CalculateDailyInterest(string secretName) {
                Customer customer = customerRepository.GetBySecretName(secretName);
                const decimal interestRate = 0.02m;
                return customer.Amount * interestRate / 365.25m;
            }
        }
        public interface ICustomerRepository {
            Customer GetBySecretName(string secretName);
        }
       [TestFixture]
        public class CustomerTest {
           
            [Test]
            public void should_calculate_daily_interest_with_mock_and_recordplayback_syntax()
            {
                MockRepository mocks = new MockRepository();
                ICustomerRepository customerRepository;
                using (mocks.Record())
                {
                    customerRepository = mocks.DynamicMock<ICustomerRepository>();
                    Expect.Call(customerRepository.GetBySecretName("8675309"))
                        .Repeat.Once()
                        .IgnoreArguments()
                        .Return(new Customer("8675309", 200000.0m));
                }
                using (mocks.Playback())
                {
                    var interestCalculator = new InterestCalculator(customerRepository);
                    var interest = interestCalculator.CalculateDailyInterest("8675309");
                    Assert.IsTrue(Math.Abs(interest - 10.951m) < 0.001m);
                }
            }
            [Test]
            public void should_calculate_daily_interest_using_stub_and_ArrangeActAssert() 
            {
                //Arrange
                ICustomerRepository customerRepository = MockRepository.GenerateStub<ICustomerRepository>();
                customerRepository.Stub(c => c.GetBySecretName("8675309"))
                    .Return(new Customer("8675309", 200000.0m));
                // Act
                var interestCalculator = new InterestCalculator(customerRepository);
                var interest = interestCalculator.CalculateDailyInterest("8675309");
                // Assert
                Assert.IsTrue(Math.Abs(interest - 10.951m) < 0.001m);
                customerRepository.AssertWasCalled(c => c.GetBySecretName("8675309"));
            }
        }
    }

    RhinoMocks offers several types of mock objects with different behavior. The "DynamicMock" is shown here:

    customerRepository = mocks.DynamicMock<ICustomerRepository>();
    

    DynamicMock is very flexible and forgiving. StrickMock forces detailed compliance; if a method is called that is not expected to be called, an error is thrown - a DynamicMock object would not care. (Think: DynamicMocks are Italian, StrickMocks are German). PartialMocks allow for a mock object's parent class to implement methods. Stubs are used for simple objects.

  5. Mocking Objects instead of Interfaces

    Here is an example of mocking a domain object instead of an external object. Often during development some DOC object's constructor may change. If you have many test cases you have to update all test tests with new constructor arguments which is such a pain. In my current project we have hundreds of test objects, so changing them all when a new field is added or removed is painful. By using RhinoMocks the constructor may change, but you don't have to update your tests.

    Three methods of testing are shown here - using a concrete, a stub, and a mock object.

    using System;
    using NUnit.Framework;
    using Rhino.Mocks;
        public interface IEmployee
        {
            DateTime Birthday { get; set; }
        }
        class Employee : IEmployee
        {
            private readonly string Name;
            public DateTime Birthday { get; set; }
    
            public Employee(string name, DateTime birthday)
            {
                Name = name;
                Birthday = birthday;
            }
        }
    class RetirementCalculator
    {
        public int CalculateDaysTilRetirement(IEmployee employee, DateTime dateTime)
        {
            return employee.Birthday.AddYears(65).Subtract(dateTime).Days;
        }
    }
        [TestFixture]
    public class RetirementCalculatorTest
        {
            [Test]
            public void calculate_days_until_retirement_using_concrete_object() {
                // Arrange
                IEmployee employee = new Employee("Peter Parker", new DateTime(1958, 06, 01));
                // Act
                DateTime dt = new DateTime(2009, 6, 18);
                int days = new RetirementCalculator().CalculateDaysTilRetirement(employee, dt);
                // Assert
                Assert.AreEqual(days, 5096);
            }
            [Test]
            public void calculate_days_until_retirement_with_stub() {
                //this is overkill for such a simple operation, but shown for example
                // imagine that an IEmployee were difficult to create, lots of constructor args...
                // Arrange
                IEmployee mockEmployee = MockRepository.GenerateStub<IEmployee>();
                mockEmployee.Birthday = new DateTime(1958, 06, 01);
                // Act
                DateTime dt = new DateTime(2009, 6, 18);
                int days = new RetirementCalculator().CalculateDaysTilRetirement(mockEmployee, dt);
                // Assert
                Assert.AreEqual(days, 5096);
            }
            [Test]
            public void calculate_days_until_retirement_using_mock_object()
            {
                MockRepository mocks = new MockRepository();
                IEmployee employee;
                using (mocks.Record()) {
                    employee = mocks.StrictMock<IEmployee>();
                    Expect.Call(employee.Birthday)
                        .Return(new DateTime(1958, 06, 01));
                }
                using (mocks.Playback()) {
                    DateTime dt = new DateTime(2009,6,18);
                    int days = new RetirementCalculator().CalculateDaysTilRetirement(employee, dt);
                    Assert.IsTrue(days == 5096);
                }
            }
            
        }
    phonebooth
  6. Making sure certain methods are called.

    In the contrived example below we make sure the "logger.Log()" method is called.

    using System;
    using NUnit.Framework;
    using Rhino.Mocks;
    
    namespace SimpleBank {
        public class Customer {
            public readonly string SecretName;
            public readonly decimal Amount;
            public Customer(string secretName, decimal amount)
            {
                SecretName = secretName;
                Amount = amount;
            }
        }
        public class InterestCalculator {
            readonly ICustomerRepository CustomerRepository;
            private readonly ILogger Logger;
            public bool WriteLogMessage { get; set;}
    
            public InterestCalculator(ICustomerRepository customerRepository, ILogger logger, bool writeLogMessages) {
                this.CustomerRepository = customerRepository;
                this.Logger = logger;
                WriteLogMessage = writeLogMessages;
            }
            public decimal CalculateDailyInterest(string secretName)
            {
                Customer customer = CustomerRepository.GetBySecretName(secretName);
                const decimal interestRate = 0.02m;
                decimal interestEarned = customer.Amount*interestRate/365.25m;
                if(WriteLogMessage)
                {
                    Logger.Log("customer " + customer.SecretName + " earned " + interestEarned);
                }
                return interestEarned;
            }
        }
    
        public interface ILogger
        {
            string Log(string message);
        }
    
        public interface ICustomerRepository { 
            Customer GetBySecretName(string secretName);
        }
        [TestFixture]
        public class CustomerTest 
        {
            [Test]
            public void should_calculate_daily_interest_and_call_logger()
            {
                MockRepository mocks = new MockRepository();
                ICustomerRepository customerRepository;
                ILogger logger;
                using (mocks.Record())
                {
                    customerRepository = mocks.StrictMock<ICustomerRepository>();
                    Expect.Call(customerRepository.GetBySecretName("xyz"))
                        .Repeat.Once()
                        .IgnoreArguments()
                        .Return(new Customer("8675309", 200000.0m));
                    logger = mocks.StrictMock<ILogger>();
                    Expect.Call(logger.Log("message"))
                        .Repeat.Once()
                        .IgnoreArguments()
                        .Return("message");
                }
                using (mocks.Playback())
                {
                    var interestCalculator = new InterestCalculator(customerRepository,logger,true);
                    var interest = interestCalculator.CalculateDailyInterest("8675309");
                    Assert.IsTrue(Math.Abs(interest - 10.951m) < 0.001m);
                }
            }
            //this throws an exception because the logger is expected to be called and its not
            [Test]
            [ExpectedException(typeof(Rhino.Mocks.Exceptions.ExpectationViolationException))]
            public void should_calculate_daily_interest_and_NOT_call_logger() {
                MockRepository mocks = new MockRepository();
                ICustomerRepository customerRepository;
                ILogger logger;
                using (mocks.Record()) {
                    customerRepository = mocks.StrictMock<ICustomerRepository>();
                    Expect.Call(customerRepository.GetBySecretName("xyz"))
                        .Repeat.Once()
                        .IgnoreArguments()
                        .Return(new Customer("8675309", 200000.0m));
                    logger = mocks.StrictMock<ILogger>();
                    Expect.Call(logger.Log("message"))
                        .Repeat.Once()
                        .IgnoreArguments()
                        .Return("message");
                }
                using (mocks.Playback()) {
                    var interestCalculator = new InterestCalculator(customerRepository, logger, false);
                    var interest = interestCalculator.CalculateDailyInterest("8675309");
                    Assert.IsTrue(Math.Abs(interest - 10.951m) < 0.001m);
                }
            }
        }
    }
    

    (Note from the future: A more modern way to do this now is using "Assert.Throws()").

     Assert.Throws<Exception>(() => 
                new TableFormatCodes("..."));
    

    Besides "mocks.Record()", you can also use "mocks.Ordered()" to insure statements are executed in a particular order.

    Instead of throwing an exception as shown above, we could just tell RhinoMocks that we don't expect logger to be called with "Repeat.Never()":

    logger = mocks.StrictMock<ILogger>();
    logger.Log("message");
    LastCall.IgnoreArguments().Repeat.Never();
    
  7. How to throw an exception in a mock object call

    This is important when testing external systems which are difficult to set to throw an exception. For example, you could turn off your database to test a scenario, but that is very labor intensive and not easily automated. It's better to use a mock object that throws an exception and then you can see how gracefully your code handles that situation. How does your app work when an OutOfMemory exception is thrown?

    [Test]
    [ExpectedException(typeof(Exception))]
    public void should_throw_an_exception_when_calling_database() {
        MockRepository mocks = new MockRepository();
        ICustomerRepository customerRepository;
        ILogger logger;
        using (mocks.Record()) {
            customerRepository = mocks.StrictMock<ICustomerRepository>();
            Expect.Call(customerRepository.GetBySecretName("xyz"))
                .Repeat.Once()
                .IgnoreArguments()
                .Throw(new Exception("something bad happened"));
            logger = mocks.StrictMock<ILogger>();
            logger.Log("message");
            LastCall.IgnoreArguments();
        }
        using (mocks.Playback()) {
            var interestCalculator = new InterestCalculator(customerRepository, logger, false);
            var interest = interestCalculator.CalculateDailyInterest("8675309");
            Assert.IsTrue(Math.Abs(interest - 10.951m) < 0.001m);
        }
    }
  8. How often was a method called?

    Object "mailSenderStub" has a method "Send". Let's test to see if it was called 3 times.

    var mailSenderStub = MockRepository.GenerateStub<IMailSender>();
    ...
    mailSenderStub.AssertWasCalled(x => x.Send(Arg<MailMessage>.Is.Anything),
    o => o.Repeat.Times(3));
    
  9. What arguments were passed to a stub?

    mailSenderStub.GetArgumentsForCallsMadeOn(x => x.Send(Arg<MailMessage>.Matches(
    mm => mm.To[0].Address == "[email protected]" )));