Since the documentation is a bit rudimentary over at ayende's site, I've decided to try to cover some of the broad range of functionality of the kick-ass mocking framework that is Rhino Mocks.
If you don't know what a mock is and which types of test doubles that there are, read this article by Fowler first.
This article will introduce us to Rhino Mocks somewhat, later I have plans to elaborate on more advanced topics like constraints etc. But let's dig into the code now. Let's say that we're building some kind of order application, where the user enters an order and adds a number of order lines to that order, and then saves. What I'd like to test now, is that the user can't add an order line to the order where the order line has no product (i.e. it's null). The base for this test is basically this:
namespace OrderApplication { public interface IOrderLine { float? Amount { get; set; } IProduct Product { get; set; } } }
namespace OrderApplication { public class Order { public Order() { _orderLines = new List<IOrderLine>(); } private List<IOrderLine> _orderLines; public void AddOrderLine(IOrderLine orderLine) { _orderLines.Add(orderLine); } public IList<IOrderLine> OrderLines { get { return _orderLines; } } } }
In the code above, all that's done is that the AddOrderLine method will take an IOrderLine. What I'd like to do is to check, when adding the OrderLine to the Order, is that the Product property on the IOrderLine isn't null.
The first thing to check for is that the AddOrderLine method should check the Product property on the order line added. This will be accomplished by doing something like this:
using NUnit.Framework; using Rhino.Mocks; namespace OrderApplication.Tests { [TestFixture] public class OrderBehaviour { private MockRepository _mocks; [SetUp] public void SetUp() { _mocks = new MockRepository(); } [Test] public void ShouldCheckForProductWhenAddingOrderLine() { //the orderline to add IOrderLine orderLine = _mocks.CreateMock<IOrderLine>(); //the order to test Order order = new Order(); using (_mocks.Record()) { Expect.Call(orderLine.Product).Return(_mocks.Stub<IProduct>()); } using (_mocks.Playback()) { order.AddOrderLine(orderLine); } } } }
In the code above I add an expectation that the AddOrderLine method should call orderLine.Product, and I make it return just something, by having Rhino Mocks creating a stub for me. When running the test, I get a failure; since we added the expectation that the Order instance should get the Product of the OrderLine, and it's not done (look at the Order code above), the NUnit test fails. In order to make this work, we'll add some code:
public void AddOrderLine(IOrderLine orderLine) { if (orderLine.Product == null) return; _orderLines.Add(orderLine); }
I only added a null check, and now the test passes (since the Order class gets the Product property). But perhaps we'd like the application not to fail silently, but instead throw an exception? All right, in the spirit of TDD, we'll add a test for this:
[Test, ExpectedException(typeof(ArgumentException))] public void AddingOrderLineWithoutProductWillFail() { //the orderline to add IOrderLine orderLine = _mocks.CreateMock<IOrderLine>(); //the order to test Order order = new Order(); using (_mocks.Record()) { Expect.Call(orderLine.Product).Return(null); } using (_mocks.Playback()) { order.AddOrderLine(orderLine); } }
Now, I've added the attribute ExpectedException to fail the test if the called code doesn't throw an exception. Notice that the expectation line now says .Return(null), which basically will make Rhino.Mocks return null when the Product property is looked up. Running this test directly will of course fail, since we don't throw the ArgumentException in the AddOrderLine method. After adding it, the method will look like this:
public void AddOrderLine(IOrderLine orderLine) { if (orderLine.Product == null) throw new ArgumentException("Product is null!"); _orderLines.Add(orderLine); }
So wrapping up, I've shown you how to write test code in which we add method call expectations. There are more to show, though, Rhino Mocks have a really nice Constraints feature, where we can add constraints for specific parameters in a method call. Hopefully I'll get to that in time.
0 kommentarer:
Post a Comment