Monday, December 10, 2007

Open Source Rocks!

I've recently began seriously investigating mocking frameworks (See NMock and Rhino Mocks specifically). I've looked at mock frameworks before, but I was quick to dismiss them. I never really saw the benefit. I honestly don't think I really understood what they were used for.

At first I thought maybe it was used to automatically create my business objects so that I would have full object graphs in memory. This is something we use a lot to perform mapping file tests, it turned out the tools didn't really fit for this purpose, and I pushed them aside.

Now, I spent some time again wondering if I missed the big picture when I looked before. After all this was years ago that I last researched mock frameworks. While the NMock interface seemed pretty clean to me I didn't like the use of strings. Rhino Mock's ability to use strongly typed method calls was a deal sealer. How could I use a weakly typed mock framework when a popular strongly typed variety exists?

I've decided that mock frameworks come in handy while performing unit tests which would otherwise use external resources. Previously I always skipped testing these methods and moved as much of the business logic to methods which had no outside dependencies. I've finally seen the light and embraced the mock frameworks for what they were truly intended.

If you haven't looked at Rhino Mocks, I strongly recommend you do! Let's take a very simple example to see the power of this tool! This is a totally contrived example, so don't blame me if it is unrealistic. Let's say you have a dependency on an external pricing service which finds the best available price for a given product in your companies purchasing department. Let's define the needed interface as IPricingService


public interface IPricingService
{
decimal GetPrice(Product p);
}


A very simple service for sure. Given any product it will return the current price which we can pay for that product. We'll use this service in our AddDetail Method of our Order class. See the Order, OrderDetail and Product implementations below. Note I use public fields for brevity only.

public class Product
{
public int Id;
public string Name;
}

public class Order
{
private IPricingService pricingService;

public IList<OrderDetail> Details
= new List<OrderDetail>();

public Order(IPricingService pricingService)
{
this.pricingService = pricingService;
}

public OrderDetail AddDetail(Product product, int quantity)
{
OrderDetail detail
= new OrderDetail(
product,
quantity,
pricingService.GetPrice(product));

Details.Add(detail);
return detail;
}
}

public class OrderDetail
{
public Product Product;
public int Quantity;
public decimal Price;

public OrderDetail(Product product,
int
quantity,
decimal
price)
{
this.Product = product;
this.Quantity = quantity;
this.Price = price;
}
}


Now our job is to test the AddDetail method which we see above. Well we don't want to rely on an actual pricing service which would use in production, so instead we will resort to our trusty Rhino Mocks framework to fill in a fake implementation. Now we can test 100% of the code in our Order without worrying about external dependencies. This helps keep our unit tests focussed as well as allows us to improve our code coverage.

[TestMethod]
public void AddDetail()
{
MockRepository mocks = new MockRepository();
Product p = new Product();

IPricingService pricingService
= mocks.CreateMock<IPricingService>();

Expect.Call(pricingService.GetPrice(p)).Return(15.50M);

mocks.ReplayAll();

Order order = new Order(pricingService);
OrderDetail newDetail = order.AddDetail(p, 20);
Assert.AreEqual<decimal>(15.50M, newDetail.Price);
Assert.AreEqual<int>(20, newDetail.Quantity);

mocks.VerifyAll();
}


Take special note of the MockRepository, the CreateMock method and the Expect builder. Expect basically tells the framework that it should expect a call to the method provided in the Call() and to expect it for the specified parameter. When the mock receives that call that the provided parameter it should return the value 15.5 as the price.

ReplayAll is a bit confusing, but basically this tells Rhino Mocks to stop using the method calls for recording instead use it to return the results we configured and to track the calls which are made.

After we have called ReplayAll we make our method calls and assert that the new order detail was created correctly and added with the appropriate price which was returned by the pricing service. VerifyAll tells us that our expectations we created before replaying were met (such as expecting a call to the GetPrice method with a parameter of p.

If I didn't want to set expectations for method calls I could have opted for the SetupResults class instead of Expect class.

This is just the tip of the ice berg regarding the framework. If this peeked your interest, you owe it to yourself to take a deeper look.

If you're like me and you saw the Expect.Call() taking an execution of the method you expect a call for you asked yourself "How the heck did he do that?" When I first tried it I was blown away that it worked. I tried to come up with ideas of how it worked.

This is one of the true beauties of open source software. I can satisfy my own curiosity! After playing with the framework I downloaded the source code to check it out. The final implementation wasn't actually that complex. Basically the proxy object which the mock framework creates tracks what the last method call was and what parameters were provided to the method call. So the Call() method doesn't really do anything except return the options for the last method call which was made. The fact that it is provided as an argument to Call() is complete ignored! I view this as genius. It's really thinking out of the box in a way that I'm not so sure I would have come up with. Being exposed to code that works in different ways that I think allows me to expand my mind and come up with solutions to my own problems I may not have previously come up with.

I've also spent a considerable amount of time reviewing some of the NHibernate code. This is after all how I was able to write such a specific performance test regarding the performance of NHibernate accessors after all. I used just the specific NHibernate pieces I needed.

As software developers we are unbelievable lucky to be living in such a day and age where such great software code is available for free for us to learn from. I recommend to everyone interested in really improving their skills find an open source project which revolves around something you are interested in and take a look. There is tons of great stuff out there! We should all take advantage.

Lastly, I just want to thank Oren Eini (aka Ayende Rahien), this Rhino Mocks framework he created is truly awesome.

--John Chapman

1 comment:

jim said...

Thanks for the simple explanation!

Blogger Syntax Highliter