Welcome to EMC Consulting Blogs Sign in | Join | Help

Owain Wraggs' Blog

I have now left EMC Consulting, you can subscribe to my new blog here: http://blog.endjin.com/author/owain-wragg/

Unit Testing With Rhino Mocks

I’ve been asked on a few occasions by my colleagues how I approach unit testing with Rhino Mocks; my approach has changed since Rhino Mocks 3.5 with the new Arrange Act Assert (AAA) syntax was released, so I thought I would write about why I use Rhino Mocks and show some example tests.

The reasons I use Rhino Mocks are

  • It allows me to do interaction testing (how classes interact with each other)
  • I can control the behaviour of any dependency that the class that I’m testing has
  • It enables me to test different paths of execution easily.
  • I can do proper unit testing, e.g. my tests have no dependency on databases, configuration files or other external resources

Below is the OrderProcess class that I am going to use to demonstrate how to test using Rhino Mocks

    public class OrderProcess

    {

        private readonly IOrderRepository repository;

 

        /// <summary>

        /// Constructor

        /// </summary>

        /// <param name="repository"></param>

        public OrderProcess(IOrderRepository repository)

        {

            this.repository = repository;

            this.repository.OrderModified += new EventHandler(HandleOrderModified);

        }

 

        /// <summary>

        /// Applies a discount to an order if the

        /// order cost is greater than 1000

        /// </summary>

        /// <param name="orderId"></param>

        /// <returns></returns>

        public Order GetAndApplyDiscount(int orderId)

        {

            Order order = repository.Get(orderId);

 

            if (order != null)

            {

                if (order.Cost > 1000)

                {

                    order.Discount = 100;

                    repository.Persist(order);

                }

            }

 

            return order;

        }

 

        /// <summary>

        /// Persists a list of orders

        /// </summary>

        /// <param name="orders"></param>

        public void Persist(List<Order> orders)

        {

            List<Order> newOrders = orders.Where(order => order.Id == 0).ToList();

            List<Order> existingOrders = orders.Where(order => order.Id > 0).ToList();

 

            if (newOrders.Count > 0)

            {

                repository.Create(newOrders);

            }

 

            if (existingOrders.Count > 0)

            {

                repository.Update(existingOrders);

            }

        }

 

        /// <summary>

        /// Deletes the specified order

        /// </summary>

        /// <remarks>Throws an OrderDeletionException is order deletion fails</remarks>

        /// <param name="order"></param>

        public void Delete(Order order)

        {

            try

            {

                repository.Delete(order);

            }

            catch (Exception)

            {

                throw new OrderDeletionException();

            }

        }

 

        /// <summary>

        /// Handles the OrderModified event

        /// </summary>

        /// <param name="sender"></param>

        /// <param name="e"></param>

        private void HandleOrderModified(object sender, EventArgs e)

        {

            //Code to handled the order modified event

        }

    }

The OrderProcess class has a single dependency IOrderRepository which is shown below

    public interface IOrderRepository

    {

        event EventHandler OrderModified;

 

        Order Get(int Id);

 

        void Persist(Order order);

 

        void Delete(Order order);

 

        void Create(List<Order> orders);

 

        void Update(List<Order> orders);

    }

The tests for the GetAndApplyDiscount method need to test the following cases

  • When the repository does not contain the order and returns  null
  • When the order returned by the repository has a order cost less than 1000
  • When the order returned by the repository has a order cost greater than 1000

Below are the unit tests to satisfy the cases listed above

        /// <summary>

        /// Checks behaviour when order does not exist in the repository

        /// </summary>

        [TestMethod]

        public void GetAndApplyDiscount_When_Order_Does_Not_Exist()

        {

            //Arrange

            IOrderRepository repository = MockRepository.GenerateMock<IOrderRepository>(); //Create mock IOrderRepository so we can control its behaviour

 

            repository.Stub(x => x.Get(25)).Return(null); //Order does not exist so return null

        

            //Act

            OrderProcess objectToTest = new OrderProcess(repository);

            Order output = objectToTest.GetAndApplyDiscount(25);

 

            //Assert

            repository.AssertWasNotCalled(x => x.Persist(Arg<Order>.Is.Anything)); //Make sure Persist was not called at all

 

            Assert.IsNull(output);

        }

 

In the above test I’ve create a mock instance of the IOrderRepository to allow me to control its behaviour, next I have stubbed the IOrderRepository.Get method so that when it is called it returns null. Next I create an instance of the OrderProcess  class and call the GetAndApplyDiscount method, and finally I have asserted that the IOrderRepository.Persist method was not called.

 

        /// <summary>

        /// Check behaviour when discount should not be applied

        /// </summary>

        [TestMethod]

        public void GetAndApplyDiscount_When_Order_Cost_Less_Than_1000()

        {

            //Arrange

            IOrderRepository repository = MockRepository.GenerateMock<IOrderRepository>(); //Create mock IOrderRepository so we can control its behaviour

 

            Order order = new Order {Cost = 750, Discount = 0};

            repository.Stub(x => x.Get(25)).Return(order); //Setup order to be returned by repository

 

            //Act

            OrderProcess objectToTest = new OrderProcess(repository);

            Order output = objectToTest.GetAndApplyDiscount(25);

 

            //Assert

            repository.AssertWasNotCalled(x => x.Persist(order)); //Make sure the order was not persisted

 

            Assert.AreEqual(0, output.Discount);

        }

 

        /// <summary>

        /// Checks behaviour when discount should be applied

        /// </summary>

        [TestMethod]

        public void GetAndApplyDiscount_When_Order_Cost_Greater_Than_1000()

        {

            //Arrange

            IOrderRepository repository = MockRepository.GenerateMock<IOrderRepository>(); //Create mock IOrderRepository so we can control its behaviour

 

            Order order = new Order {Cost = 1200, Discount = 0};

            repository.Stub(x => x.Get(25)).Return(order); //Setup order to be returned by repository

 

            //Act

            OrderProcess objectToTest = new OrderProcess(repository);

            Order output = objectToTest.GetAndApplyDiscount(25);

 

            //Assert

            repository.AssertWasCalled(x => x.Persist(order)); //Make sure the order was persisted

 

            Assert.AreEqual(100, output.Discount); //Make sure the discount was added to the order

        }

Below are the Persist method tests which verify the following cases

  • New orders are passed to the IOrderRepository.Create method
  • Existing orders are passed to the IOrderRepository.Update method

        /// <summary>

        /// Checks that the behaviour for persisting new orders

        /// </summary>

        [TestMethod]

        public void Persist_When_Orders_New()

        {

            //Arrange

            IOrderRepository repository = MockRepository.GenerateMock<IOrderRepository>(); //Create mock IOrderRepository so we can control its behaviour

 

            List<Order> orders = new List<Order>

                                     {

                                         new Order {Id = 0, Cost = 1200, Discount = 0 },

                                         new Order{Id = 0, Cost = 1500, Discount = 0}

                                     };

 

            //Act

            OrderProcess objectToTest = new OrderProcess(repository);

            objectToTest.Persist(orders);

 

            //Assert

            repository.AssertWasCalled(x => x.Create(Arg<List<Order>>.List.ContainsAll(orders))); //Make sure orders were created

        }

 

        /// <summary>

        /// Checks the behaviour for existing orders

        /// </summary>

        [TestMethod]

        public void Persist_When_Orders_Already_Exist()

        {

            //Arrange

            IOrderRepository repository = MockRepository.GenerateMock<IOrderRepository>(); //Create mock IOrderRepository so we can control its behaviour

 

            List<Order> orders = new List<Order>

                                     {

                                         new Order {Id = 2, Cost = 1200, Discount = 0 },

                                         new Order{Id = 12, Cost = 1500, Discount = 0}

                                     };

 

            //Act

            OrderProcess objectToTest = new OrderProcess(repository);

            objectToTest.Persist(orders);

 

            //Assert

            repository.AssertWasCalled(x => x.Update(Arg<List<Order>>.List.ContainsAll(orders))); //Make sure orders were updated

        }

 

        /// <summary>

        /// Checks the behaviour when there is a mix of new and existing orders

        /// </summary>

        [TestMethod]

        public void Persist_When_Mixture_Of_New_And_Existing_Orders()

        {

            //Arrange

            IOrderRepository repository = MockRepository.GenerateMock<IOrderRepository>(); //Create mock IOrderRepository so we can control its behaviour

 

            Order order1 = new Order {Id = 2, Cost = 1200, Discount = 0};

            Order order2 = new Order {Id = 0, Cost = 1500, Discount = 0};

            Order order3 = new Order { Id = 32, Cost = 1200, Discount = 0 };

            Order order4 = new Order { Id = 0, Cost = 1500, Discount = 0 };

 

            List<Order> orders = new List<Order>

                                     {

                                         order1,

                                         order2,

                                         order3,

                                         order4

                                     };

 

            //Act

            OrderProcess objectToTest = new OrderProcess(repository);

            objectToTest.Persist(orders);

 

            //Assert

            repository.AssertWasCalled(x => x.Create(Arg<List<Order>>.List.ContainsAll(new List<Order>{order2, order4}))); //Make the sure correct orders were created

            repository.AssertWasCalled(x => x.Update(Arg<List<Order>>.List.ContainsAll(new List<Order> { order1, order3 }))); //Make the sure correct orders were updated

        }

In the test above I’ve again mocked the IOrderRepository, then setup a list of orders (some of which are new and some which are existing) created an instance of the OrderProcess class passed the orders to the Persist method. I then assert that both the IOrderRepository’s Create and Update methods are called and verify that the correct orders are passed to each method

Next are the tests for the Delete method which verify

  • That the IOrderRepository.Delete method is called
  • The OrderDeletionException is thrown if the IOrderRepository.Delete method throws an exception

        /// <summary>

        /// Checks behaviour when order is deleted

        /// </summary>

        [TestMethod]

        public void Delete_When_Order_Successfully_Deleted()

        {

            //Arrange

            IOrderRepository repository = MockRepository.GenerateMock<IOrderRepository>(); //Create mock IOrderRepository so we can control its behaviour

 

            Order order = new Order();

 

            //Act

            OrderProcess objectToTest = new OrderProcess(repository);

            objectToTest.Delete(order);

 

            //Assert

            repository.AssertWasCalled(x => x.Delete(order)); //Make sure repository deletes the order

        }

 

        /// <summary>

        /// Checks behaviour when the repository throws an exception

        /// when deleting an order

        /// </summary>

        [TestMethod]

        [ExpectedException(typeof(OrderDeletionException))]

        public void Delete_When_Order_Deletion_Fails()

        {

            //Arrange

            IOrderRepository repository = MockRepository.GenerateMock<IOrderRepository>(); //Create mock IOrderRepository so we can control its behaviour

            Order order = new Order();

 

            repository.Stub(x => x.Delete(order))

                .Throw(new Exception("Deletion Failed")); //Throw an exception when repository is called

 

            //Act

            OrderProcess objectToTest = new OrderProcess(repository);

            objectToTest.Delete(order);

 

            //Assert

        }

In the test above which verifies that an OrderDeletionException is thrown if the call to the IOrderRepository.Delete fails, I’ve stubbed the call to the Delete method so that when it is called an Exception is thrown and I’ve used the ExpectedException attribute to make sure the tests throws an OrderDeletionException.

Finally I need to test that the OrderProcess class subscribes to the IOrderProcess’ OrderModified event as shown in the test below

        /// <summary>

        /// Checks that the order process subscribes to the

        /// OrderModified event

        /// </summary>

        [TestMethod]

        public void Constructor_Subscribes_To_OrderModified_Event()

        {

            //Arrange

            IOrderRepository repository = MockRepository.GenerateMock<IOrderRepository>(); //Create mock IOrderRepository so we can control its behaviour

 

            //Act

            OrderProcess objectToTest = new OrderProcess(repository);

 

            //Assert

            repository.AssertWasCalled(x => x.OrderModified += Arg<EventHandler>.Is.Anything); //Make sure that the OrderModified event has been subscribed to

        }

Attached is an example project containing the code from this post.

Comments

 

dennis.loktionov said:

Great example!

January 30, 2009 21:42
Anonymous comments are disabled
Powered by Community Server (Personal Edition), by Telligent Systems