Wednesday, November 26, 2008

Write My Rhino Mocks Expect Statement

Mocking multiple calls to a complicated external dependency (using RhinoMocks for instance) can be challenging if you want to return realistic data. But if you have access to the external dependency and it has decent test data why can’t your mocking tool just call the real dependency and give you the exact Expect() and Return() statements that you need to mock it’s real state?

Well, now it can with WriteMyExpectStatement on CodePlex. How much would you expect to pay for this power? $1,000? $100? How about absolutely free! Alright, guess I've been watching too much late night TV. Let’s examine the problem via an example.

Example: Mocking a Database

Suppose you have a Northwind style database with products and orders and you want to re-order products whose inventory is low:

public class NorthwindService {
  ...
  public void ReorderLowInventoryProducts() {
    using (SqlCeConnection cn = NorthwindDao.GetConnection()) {
      NorthwindDao.Connection = cn; // simple IOC
      IEnumerable<Product> products = NorthwindDao.GetLowInventoryProducts();
      // cache lookup tables in memory
      Dictionary<int, Supplier> suppliers = NorthwindDao.GetAllSuppliers()
        .ToDictionary(k => k.SupplierID); // yayyyy, LINQ

      foreach (Product product in products) {
        Supplier supplier = suppliers[product.SupplierId];
        supplier.OrderMoreProduct(product);
      }
    }
}

It’s a simple example, but already there is a dependency between the data of the two calls that need to be mocked. Specifically GetLowInventoryProducts() specifies a SupplierId whose value must be returned by GetAllSuppliers(). This isn’t complicated enough you couldn’t mock it yourself, but you can imagine a more complicated example with multiple calls and multiple data dependencies.

RhinoMocks.PleaseJustWriteMyExpectStatementforMe = true

In order to mock the above using Rhino Mocks you would first need some code like the following:

private static void ReorderLowInventoryProductsMocked() {
  NorthwindService northwindService = new NorthwindService();

  MockRepository repository = new MockRepository();
  NorthwindDao northwindDaoMock = repository.StrictMock<NorthwindDao>();
  northwindService.NorthwindDao = northwindDaoMock;

  // put .Expect() statements here

  repository.ReplayAll();

  northwindService.ReorderLowInventoryProducts();
}

And assuming NorthwindDao’s methods are virtual you’ll get

ExpectationViolationException NorthwindDao.GetConnection(); Expected #0, Actual #1.

But if you let WriteMyExpectStatement at it using the following:

try {
  northwindService.ReorderLowInventoryProducts();
} catch (ExpectationViolationException ex) {
  NorthwindDao northwindDaoReal = new NorthwindDao();
  using (SqlCeConnection cn = northwindDaoReal.GetConnection()) {
    northwindDaoReal.Connection = cn;
    MyExpectStatement.Write(ex, "northwindDaoMock", northwindDaoReal);
  }
}

You’ll literally get an exception that looks like this:

NorthwindDao.GetConnection(); Expected #0, Actual #1.
WriteMyExpectStatement:
SqlCeConnection sqlCeConnection = new SqlCeConnection {ConnectionString = "Data Source=Northwind.sdf;Persist Security Info=False;", }
Expect.Call(northwindDaoMock.GetConnection()).Return(sqlCeConnection);

Cool huh? Now if you paste that code at “// Put .Expect() Statements Here” and run it again you’ll get:

NorthwindDao.GetLowInventoryProducts(); Expected #0, Actual #1.
WriteMyExpectStatement:
IEnumerable products = new List {new Product {ReorderLevel = 17, UnitsInStock = 25, ProductName = "Chang", Discontinued = false, ProductID = 2, SupplierId = 1, }, … };
Expect.Call(northwindDaoMock.GetLowInventoryProducts()).Return(products);

Just keep copying and pasting until all of your expect statements are there and you’re done.

Limitations

WriteMyExpectStatement knows how to generate the code to do most things and it will recurse into any complex objects you throw at it (e.g. notice how it picked up the fields in the Product class that it had no a priori knowledge of). What it can’t do as well is call methods that take complicated parameters. For instance through reflection it needs to call GetLowInventoryProducts() on northwindDaoReal. If we abandoned InversionOfControl (IOC) and had SqlCeConnection be a parameter to GetLowInventoryProducts(), then WriteMyExpectStatement would have absolutely no idea that it would need to call SqlCeConnection.Open() and so would fail. So as long as you primarily use primitives as parameters to the things you mock you should be able to use WriteMyExpectStatement. And if you have bigger requirements let me know (“codeplex@l” + “eerichardson.c” + “om”), I’d be happy to let you in to the project.

Summary

I realize most situations don’t really need code generation for their expect statements, but if for instance you have existing integration tests that you want to convert to mocks, then something like this is essential. It was for me anyway.

No comments: