Sunday, 16 September 2007

Unit testing with Genome

In this article, I will describe how an application implemented with Genome can be tested by means of unit testing. By stealing (and also modifying) the sample domain from Fowler, I’ll demonstrate this with a simple WebShop application. The use case that we are going to investigate is the ordering process itself.

Imagine a WebShop where the user has collected quantities of products in a shopping cart, and now s/he is going to order the goods collected. Usually, many different steps need to be carried out in the process of ordering, such as debiting the customer’s account, decreasing the stock, calculating the prices, persisting the order and also sending a confirmation e-mail. Now, since this is a very important part of our application, we would like to cover the implementation with proper unit tests.

Unit testing vs. Integration Testing

Many articles deal with the difference between unit testing and integration testing. What is certain is that integration tests cannot be avoided and must be automated as far as possible. (At the end of the day, the end user will use the software in an integration environment, and knowing that all unit tests were passed will not really satisfy him...). Sometimes it is easy to set up a reliable integration environment, and in this case it is probably better to put more effort in the automated integration test than the unit tests. Such an environment can even be an application that just reads and saves data to its own database.

In many cases, it is hard to set up a reliable integration environment. Reliability is very important. If the test system is not reliable enough, the team will waste too much time investigating false error reports, or even worse, will get used to failing test cases and will not pay enough attention to a breaking build. Do not forget that creating unit tests is quite an effort. If you do not use them, it is a wasted effort.

There are even cases when it is (almost) impossible to automate integration tests, for example to test the confirmation e-mail which is sent to the customer.

Some people also opt for unit tests because running integration tests might be slow, or because critical conditions (such as how the application reacts when the database is not accessible) can be better tested with unit tests. Of course, these arguments can only be judged within the scope of the concrete application requirements.

If you use an O/RM such as Genome to write an integration test that uses the database, it is relatively easy. Genome provides API methods to re-create the database, and also you can use the O/RM API to create and verify the test data. With these helper methods, you can easily build up a stub-based test (Fowler calls it "classicist" in his article). You create the test data, then execute your unit, then verify the result.

To use the "mockist" style, or to avoid dependency on the database in your unit tests, is a bit more complex. In this article, I will show how this can be achieved, based on concrete examples.

Problems with unit testing for O/RM applications

The basic concept behind "mockist" unit testing is that you test your piece of code in such a way that you use mock implementations for all modules that your code depends on. This rule requires the code to be designed in a certain way; otherwise, you will not be able to isolate meaningful units for testing (in the worst case, the entire application is a unit :-). These design requirements are as follows:

  • Structure the code into isolated modules.
  • Use dependency injection to communicate between your modules.
  • Try to avoid circular dependencies between modules.

Well, the good news is that these rules are sound, even if you do not use unit testing.

In simple 3-tier applications, the layers can usually serve as modules. This means that you can write unit tests for the business layer, with the data access layer mocked. Let us concentrate on testing only the business layer codes now, as GUI and DAL testing brings in too many other problems that are beyond the scope of this article.

By using O/RM, you reduce the cost of writing the data access layer. However, some O/RMs such as Genome can do much more. It can provide you object life-time management, possibilities for injecting different aspects (like validation and logging), the possibility to automate coding tasks with custom extensions etc. Mats Helander once mentioned that he even considered using the term "domain object manager" instead of O/RM...

Of course, the more you use these additional functionalities, the more you smooth the boundary between the data access layer and the business layer, which makes life easier, but the "mockist" unit testing harder. I think, however, that by following a few guidelines, you can successfully isolate the parts that belong to the business logic and even cover them with "mockist" unit tests. Such guidelines might look like this:

  • Isolate queries and dynamic query building code into separate classes (see QueryProvider infrastructure)
  • Use validation only as a secondary (safety) check
  • Isolate the transitioning code from the logic
  • etc.

And again, the good news is that these rules are sound, even if you do not use unit testing.

Walkthrough: WebShop Sample

Application overview

As I have mentioned, we will use a simple WebShop application to demonstrate the possibilities of unit testing with Genome. You can find the full source code of this sample attached. The application uses a few simple business classes to model the shop, as you can see in the following picture:

Sample domain model

Note: the ShoppingCart and ShoppingCartItem classes are transient classes (i.e. they are not persisted to the database).

The business logic for the ordering process, which we are going to test, is located in a service class, called "OrderingService". The ordering service has a very simple interface to the UI:

public interface IOrderingService
{
void MakeOrder(ShoppingCart cart);
}

The MakeOrder method is supposed to carry out all tasks that are required to place the order. Right now, for the sake of simplicity, we will only concentrate on creating and persisting the order, calculating the shipping costs and sending the notification e-mail.

Initially, our method looked like this:

public class OrderingService : IOrderingService
{
    private DataDomain dataDomain;
    private IEMailService eMailService;

    public OrderingService(DataDomain dataDomain, IEMailService eMailService)
    {
        this.eMailService = eMailService;
    }

    public void MakeOrder(ShoppingCart cart)
    {
        using(Context.Push(ShortRunningTransactionContext.Create()))
        {
            //calculate shipping costs
            decimal shippingCost = cart.Customer.City == "Budapest" ? 10 : 20;

            //store order record
            Order order = dataDomain.New<Order>();
            order.Customer = cart.Customer;
            order.ShippingCost = shippingCost;
            foreach (ShoppingCartItem cartItem in cart.Items)
            {
                OrderItem orderItem = dataDomain.New<OrderItem>();
                orderItem.Product = cartItem.Product;
                orderItem.Price = cartItem.Product.UnitPrice;
                orderItem.Quantity = cartItem.Quantity;
                order.Items.Add(orderItem);
            }

            //sending confirmation mail
            eMailService.SendMail(cart.Customer.EMail, 
                order.Items.Count + " products successfully ordered!");

            Context.Current.Commit();
        }
    }
}

As you can see from this code, the OrderingService class already uses dependency injection to interact with the e-mail sending service to make testing easier.

Set up the test environment

To create unit tests, we will use the NUnit open source framework, but the code can be adapted to any other unit testing framework just as easily.

We have a project called "Shop.Business.Test" which contains a test fixture "OrderingServiceTest".

Setup Rhino Mocks mocking framework

To create and use mock implementations, we will use the Rhino Mocks mock objects framework. Rhino Mocks (apart from being a properly working mocking framework) has some very nice unique features:

  • It not only mocks interfaces, but classes as well (Genome persistent classes are abstract classes).
  • It uses recording to capture expectations, which means that to put an expectation on a method or property, the actual call has to be made. This works very well with the code refactoring tools, unlike some mocking frameworks where you have to form your expectations by using the method or property name as a string.
  • It is very easy to simulate stub-like behaviour for the mocks you create.

To use Rhino Mocks, you have to instantiate a MockRepository, create your mock instances and record the expectations. Once you have finished this, you call the mockRepository.ReplayAll() method and start testing your code. While the test is running, the mock framework will test whether the recorded calls satisfy the expectations. Finally, you have to call mockRepository.VerifyAll(), which ensures that the test is complete, i.e. that all expected calls were actually fired.

To demonstrate this, let us see a very simple unit test. Let us suppose that we want to test the very complex logic of composing the confirmation e-mail message ("<# of items> items successfully ordered"). In order to test this code, we have to extract it into a separate method:

public void SendConfirmationMail(string eMailAddress, int itemCount)
{
    eMailService.SendMail(eMailAddress, 
        itemCount + " items successfully ordered!");
}

Now let us write a unit test for the basic success case:

[TestFixture]
public class OrderingServiceTest
{
    private MockRepository mockRepository;

    [SetUp]
    public void Setup()
    {
        mockRepository = new MockRepository();
    }

    [Test]
    public void ShouldIncludeItemCountInConfirmationMail()
    {
        // create mocks and record expectations
        IEMailService eMailServiceMock = mockRepository.CreateMock<IEMailService>();

        // we will expect this call
        eMailServiceMock.SendMail("tired@sql", "42 items successfully ordered!");

        // end recording
        mockRepository.ReplayAll();

        // do the test
        OrderingService orderingService = new OrderingService(eMailServiceMock);
        orderingService.SendConfirmationMail("tired@sql", 42);

        // verify that the call to e-mail service was really made
        mockRepository.VerifyAll();
    }
}

Note: since we need a mock repository for all of our tests, we have moved the repository initialisation into the test case setup part.

Naming your unit test is important. Dan North (and probably others as well) once explained to me that good naming can help you write and maintain the most effective unit tests. He believed in a naming convention where the test fixture name and the test name together form a sentence that explains the meaning of the unit test (in this case: "Ordering service [test] should include [the] item count in [the] confirmation mail"). He also highlighted that the word "should" best describes the purpose of the unit test. He said that the word "should" holds the possibility that if the unit test fails, then either the code is wrong or the requirements have changed. Read more about this at: http://dannorth.net/introducing-bdd/

Using persistent classes as stubs

The main purpose of persistent classes, even if you use an O/RM, is to hold the state that is persisted in the database as table records. In many cases, the business logic can be tested most efficiently if you use the persistent classes as simple state containers (aka entities).

Genome provides reach functionality to manage the different operations of the persistent classes (such as the possibility of intercepting property access etc.). Genome implements all this functionality by deriving a proxy class from the abstract persistent class. For this reason, the instances of these classes cannot be used directly as detached standalone entities.

In this situation, the subbing functionality of the Rhino Mocks framework works the best (although you can simulate similar behaviour with other mocking frameworks as well, but with much more coding).

Let us suppose that we want to test the shipping cost calculation within the ordering process. (This webshop is operated by me, so if you order from anywhere else but the beautiful city of Budapest, you have to pay double price. Sorry...). After extracting the logic, we have the following method to test:

public decimal CalculateShippingCost(Customer customer)
{
    return customer.City == "Budapest" ? 10m : 20m;
}

To test this method for a customer in Budapest, you can use the following test:

[Test]
public void ShouldUseNormalShippingCostForCustomersInBudapest()
{
    // create mocks
    Customer customer = mockRepository.Stub<Customer>();
    customer.City = "Budapest";

    // do the test
    OrderingService orderingService = new OrderingService();
    decimal shippingCost = orderingService.CalculateShippingCost(customer);

    // verification
    Assert.AreEqual(10m, shippingCost);
}

As you can see here, we did not call the ReplayAll() and VerifyAll() methods since we did not record any expectations. Instead of that, we created a customer stub to call the calculation and verified the returned value with the NUnit assertion method.

Stubs work like normal POCOs. You can set the properties of the stubs and they will return the value that was set. Both getter and setter can be called as many times as you want.

As another example, you can imagine a method which not only calculates the shipping cost, but also sets the value into the order record. For such a method, a test case could look like:

[Test]
public void ShouldSetNormalShippingCostForCustomersInBudapest()
{
    // create mocks
    Customer customer = mockRepository.Stub<Customer>();
    customer.City = "Budapest";

    Order order = mockRepository.Stub<Order>();

    // do the test
    OrderingService orderingService = new OrderingService();
    orderingService.SetShippingCost(customer, order);

    // verification
    Assert.AreEqual(10m, order.ShippingCost);
}

Note: Rhino Mocks also checks whether a value was set at all for the property when you are trying to get it, and throws an exception when you try to access an uninitialised property. Therefore this unit test also checks whether the SetShippingCost() method really sets the property on the order class.

Mocking Object Life-time Actions

If we move on in the MakeOrder() method, the next step to be tested is creating the order record. So we again refactor the code a little bit, to make it testable:

public Order CreateOrderTrunk(Customer customer, decimal shippingCost)
{
    Order order = dataDomain.New<Order>();
    order.Customer = customer;
    order.ShippingCost = shippingCost;
    return order;
}

As you can see here, we are calling the DataDomain.New() method from the Genome API to obtain a new persistent instance that has to be inserted into the database. Although the DataDomain is an injected dependency of the OrderingService, the New<> method cannot be mocked as it is a non-virtual one. And even if you could mock it, it would probably too generic method to provide meaningful mocks (imagine the unit test where we test the order and order item creations at once. It would be hard to separate the order’s New() request and the order items in a clean way).

To solve this problem, we can introduce a layer boundary to isolate these data-access related calls, so we introduce a new interface:

public interface IOrderingServiceDataAccess
{
    Order CreateOrder();
    OrderItem CreateOrderItem();
}

Then, instead of injecting the DataDomain into our service class, we can inject an instance of the IOrderingServiceDataAccess interface. To create the "real" implementation, which uses a DataDomain, is quite simple:

public class OrderingServiceDataAccess : IOrderingServiceDataAccess
{
    private readonly DataDomain dataDomain;

    public OrderingServiceDataAccess(DataDomain dataDomain)
    {
        this.dataDomain = dataDomain;
    }

    public Order CreateOrder()
    {
        return dataDomain.New<Order>();
    }

    public OrderItem CreateOrderItem()
    {
        return dataDomain.New<OrderItem>();
    }
}

With this isolation, we can already write our unit test for the CreateOrderTrunk() method:

[Test]
public void ShouldSetCustomerAndDateForOrder()
{
    // create mocks and record expectations
    Customer customer = mockRepository.Stub<Customer>();

    IOrderingServiceDataAccess dataAccessMock =
        mockRepository.CreateMock<IOrderingServiceDataAccess>();

    // expectation: the CreateOrder should be called once, 
    // and should return an order stub
    Expect.Call(dataAccessMock.CreateOrder()).Return(mockRepository.Stub<Order>());

    // end recording
    mockRepository.ReplayAll();

    // do the test
    OrderingService orderingService = new OrderingService();
    orderingService.DataAccess = dataAccessMock;

    Order createdOrder = orderingService.CreateOrderTrunk(customer, 10);

    // verification
    Assert.AreEqual(customer, createdOrder.Customer);
    Assert.AreEqual(DateTime.Today, createdOrder.OrderDate);

    mockRepository.VerifyAll();
}

Note: since the number of injected dependencies in our OrderingService increases, it make sense to change the injection model from constructor-based to a model where you can inject the dependencies by setting appropriate properties.

If we run this test case, it fails because the createdOrder.OrderDate was not set. Actually, it was originally set, but in the Order constructor, not in the CreateOrderTrunk method. Now, when we have isolated the DataDomain.New() method call, it seems that we simply threw out the baby with the bath water... we did too much.

In order to isolate the construction of an object from the business logic, the business logic’s initialisation has to be moved away from the constructor as well (to an Initialize() method, for example). In our case, we put it in the CreateOrderTrunk now:

public Order CreateOrderTrunk(Customer customer, decimal shippingCost)
{
    Order order = dataAccess.CreateOrder();
    order.OrderDate = DateTime.Today;
    order.Customer = customer;
    order.ShippingCost = shippingCost;
    return order;
}

Note: Although the code is proper, this test case will sometimes fail. You know when? When midnight is between the test call and the verification call. It will not happen often, but it will... And this is just to highlight another (usually forgotten) dependency: the TIME.

The isolation technique that we have used here can also be used to handle other object life-time events, for example deleting an object.

Stubbing collections

As the next step of testing the ordering process, we would like to test the part when the MakeOrder() method creates the order items based on the items in the cart, and associates them to the created order trunk. The code, extracted from the original method, is as follows:

public void CreateOrderItems(List<ShoppingCartItem> cartItems, Order order)
{
    foreach (ShoppingCartItem cartItem in cartItems)
    {
        OrderItem orderItem = dataAccess.CreateOrderItem();
        orderItem.Product = cartItem.Product;
        orderItem.Price = cartItem.Product.UnitPrice;
        orderItem.Quantity = cartItem.Quantity;
        order.Items.Add(orderItem);
    }
}

Genome provides the Collection<T> class to model the most commonly used association types (1-n and n-m). This class is frequently used in applications developed with Genome.

Our WebShop also uses Collection<T> to store an order’s items. This is a 1-n relation in the domain model and is represented by a foreign key field in the OrderItem table. Fortunately, the OR/M hides this.

First, let us try to implement the test in the "classicist" way, so we perform the CreateOrderItems() operation and verify the result afterwards.

To achieve this, we create an Order stub that we pass to the CreateOrderItems() method. However, without specifying what the Items property of the order stub should return, this returns a null reference, so our implementation fails with a NullReferenceException on the order.Items.Add(...) statement.

To stub the Items collection too, we have to use a collection stub. You will find a project named "TechTalk.Genome.Extensions.Testing" in the attached solution. This project contains a helper class "CollectionStub<T>" that can be used for this purpose. (We cannot use Rhino Mocks’ stubs to stub Collection<T> because it can be used for entity-like objects only, but not for collections.)

Using the CollectionStub<T>, the test method looks like this:

[Test]
public void ShouldCreateOrderItemForCartItems()
{
    // create mocks and record expectations
    IOrderingServiceDataAccess dataAccessMock =
        mockRepository.CreateMock&t;IOrderingServiceDataAccess>();

    Order order = mockRepository.Stub<Order>();
    SetupResult.For(order.Items).Return(new CollectionStub<OrderItem>());

    List<ShoppingCartItem> items = new List<ShoppingCartItem>();
    items.Add(new ShoppingCartItem(GetProductStub("shoe", 20), 3));
    items.Add(new ShoppingCartItem(GetProductStub("shine", 10), 4));

    // expectation: the CreateOrderItem should be called twice, 
    // and should return new order item stubs
    Expect.Call(dataAccessMock.CreateOrderItem()).
        Return(mockRepository.Stub<OrderItem>());
    Expect.Call(dataAccessMock.CreateOrderItem()).
        Return(mockRepository.Stub<OrderItem>());

    // end recording
    mockRepository.ReplayAll();

    // do the test
    OrderingService orderingService = new OrderingService();
    orderingService.DataAccess = dataAccessMock;

    orderingService.CreateOrderItems(items, order);

    // verification
    Assert.AreEqual(2, order.Items.Count);
    Assert.AreEqual(3, order.Items[0].Quantity);
    Assert.AreEqual("shoe", order.Items[0].Product.Name);

    mockRepository.VerifyAll();
}

As we will need sample products in several future tests, I have also created a GetProductStub() method that creates a Rhino Mocks product stub with the specified parameters.

Note: You can repeat expectations in Rhino mocks. In the first version, I used this for the test and specified the expectation on CreateOrderItem() method in this manner:

Expect.Call(dataAccessMock.CreateOrderItem()).
    Return(mockRepository.Stub<OrderItem>()).Repeat.Twice();
    

Although the code looks much nicer this way, you will experience some strange behaviour: both CreateOrderItem() calls will return the same order item stub instance! (Well, it is obvious after you have made this mistake once.) As a general suggestion, I can say that you should not use the "Repeat" function of an expectation if your expectation is also used to return a value (although it will work for value types).

As you can see, we have checked the count of the order items and the details of the first order item in the verify section. We could have also checked the second item, or even created another test with three items in the cart... but you have to stop somewhere. You have to bow to the fact that you will never be able to test all different cases.

Mocking collections

Going about it the "classicist" way, as with the previous test, you can only check the result of the operations performed on the collection. This is usually enough, but there are cases when you explicitly want to check how many times and in which order the tested code called the Add() or the Remove() method.

In this case, we want to create a mock from Collection<T> class using Rhino Mocks. We would need a default constructor on Collection<T> to do this, but there is one small problem: it does not have one. Technically, it is possible to create mocks without default constructors as well, but we do not want to obscure our tests by passing dummy parameters every time a collection is mocked.

To support this scenario, the "TechTalk.Genome.Extensions.Testing" project contains a MockCollection<T> class that provides a proper default constructor and can be used for mocking.

Let us now see how the previous test can be implemented in a "mockist" manner (the changes are highlighted in bold font):

[Test]
public void ShouldCreateOrderItemForCartItems_Mockist()
{
    // create mocks and record expectations
    IOrderingServiceDataAccess dataAccessMock =
        mockRepository.CreateMock<IOrderingServiceDataAccess>();

    Order order = mockRepository.Stub<Order>();

    Collection<OrderItem> orderItemsMock = 
      mockRepository.PartialMock<MockCollection<OrderItem>>();
    SetupResult.For(order.Items).Return(orderItemsMock);

    List<ShoppingCartItem> items = new List<ShoppingCartItem>();
    items.Add(new ShoppingCartItem(GetProductStub("shoe", 20), 3));
    items.Add(new ShoppingCartItem(GetProductStub("shine", 10), 4));

    // expectation: the CreateOrderItem should be called twice, 
    // and should return new order item stubs
    OrderItem orderItem1 = mockRepository.Stub<OrderItem>();
    OrderItem orderItem2 = mockRepository.Stub<OrderItem>();
    Expect.Call(dataAccessMock.CreateOrderItem()).Return(orderItem1);
    Expect.Call(dataAccessMock.CreateOrderItem()).Return(orderItem2);

    // expectation: the Add() method is called with the order items
    orderItemsMock.Add(orderItem1);
    orderItemsMock.Add(orderItem2);

    // end recording
    mockRepository.ReplayAll();

    // do the test
    OrderingService orderingService = new OrderingService();
    orderingService.DataAccess = dataAccessMock;

    orderingService.CreateOrderItems(items, order);

    // verification
    Assert.AreEqual(3, orderItem1.Quantity);
    Assert.AreEqual("shoe", orderItem1.Product.Name);

    mockRepository.VerifyAll();
}

Since we have now verified that the Add() method will be called with the different order items, we do not have to (and we cannot) verify the count of the items collection anymore. Of course you can also put expectations on the Remove() or Clear() methods as well. Keep in mind that enumeration is also a method call (GetEnumerator) that you might need to mock in certain situations as well.

Note: The expectations for the Add() method look a bit strange, as you do not see any "expect" operation around. This is a strange side-effect of the recording methodology. These method calls are actually expectations: they expect that the method will be called, and that it will be called with the parameters we have specified. If you need to specify more expectations on void methods, you can use the "LastCall" class.

Note: Because some properties and methods of the Collection<T> base class (e.g. the Count property) are not virtual, Rhino Mocks cannot mock them as they are. To mock these members, MockCollection<T> provides mockable property and member variants that can be used instead of the original member. To use these methods, the mock has to be created with the PartialMock() factory method instead of CreateMock():

Collection<OrderItem> orderItemsMock = 
      mockRepository.PartialMock<MockCollection<OrderItem>>();
Expect.Call(orderItemsMock.MockCount).Return(42);

Note: By default, Rhino Mocks interprets the expectations as unordered expectations. This means that it will not verify whether the recorded method calls will occur in the order in which have recorded them. In our concrete case, changing the Add() expectations to

orderItemsMock.Add(orderItem2);
            orderItemsMock.Add(orderItem1);
    

will also result in a successful test. If you want to check the order of the calls as well, the expectations have to be wrapped into an ordered block:

using (mockRepository.Ordered())
{
                orderItemsMock.Add(orderItem1);
                orderItemsMock.Add(orderItem2);
}
    

However in this case, the expectations on the CreateOrderItem() call have to be merged into the call chain as well.

Testing Transactional Behaviour

Testing transactional behaviour is generally difficult, as it is quite complex to mimic some (otherwise important) parts of the transactions, like isolation mode. Therefore, we usually test this scope only in an integration environment (with the database).

If you need to test these parts with unit tests regardless of this (for example because you need to test your application’s error tolerance), I recommend isolating the transaction code (e.g. the Context creation and Commit calls) to a project-specific service class that you can mock for your unit tests.

In our sample, we have extended the IOrderingServiceDataAccess interface to cover the transactional aspects.

public interface IOrderingServiceDataAccess
{
    IDisposable PushUpdateContext();
    void CommitChanges();

    Order CreateOrder();
    OrderItem CreateOrderItem();
}

We have also refactored the MakeOrder() methoAlso in order to be able to test the business logic without the transactional aspects.

public void MakeOrder(ShoppingCart cart)
{
    using(dataAccess.PushUpdateContext())
    {
        // the business logic of the ordering process
        MakeOrderLogic(cart);

        // committing the transaction
        dataAccess.CommitChanges();
    }
}

In some cases, it is also useful to move the transactional aspect into a completely separated layer that uses a configurable business logic implementation (using dependency injection again). This way, the transactional behaviour (such as the transaction being rolled back when the business logic throws an exception) can be tested even without dealing with the dependencies of the logic itself.

Now we create a test that does the same, even without separating the transaction layer, by using Rhino Mocks’ partial mock feature. To do this, you need to make the methods that are to be mocked virtual (in this case, MakeOrderLogic()). Well, this is a little bit hacky... maybe I should have separated it in different layers. Anyway, you can imagine that for yourself.

[Test]
[ExpectedException(typeof(Exception), "simulated exception")]
public void ShouldRollbackTransactionWhenExceptionThrown()
{
    // create mocks and record expectations
    IOrderingServiceDataAccess dataAccessMock =
        mockRepository.CreateMock<IOrderingServiceDataAccess>();

    // the MakeOrderLogic() method of the ordering service instance 
    // will be mocked, the rest remains the same
    OrderingService orderingServicePartialMock = 
        mockRepository.PartialMock<OrderingService>();

    // expectation: the business logic throws an exception
    orderingServicePartialMock.MakeOrderLogic(null);
    LastCall.IgnoreArguments().Throw(new Exception("simulated exception"));

    // expectation: the transaction is rolled back
    IDisposable contextDisposerMock = mockRepository.CreateMock<IDisposable>();
    contextDisposerMock.Dispose();

    // expectation: PushUpdateContext returns our mocked context disposer
    Expect.Call(dataAccessMock.PushUpdateContext()).Return(contextDisposerMock);

    // end recording
    mockRepository.ReplayAll();

    // do the test
    orderingServicePartialMock.DataAccess = dataAccessMock;

    try
    {
        orderingServicePartialMock.MakeOrder(null);
    }
    finally
    {
        // verification
        mockRepository.VerifyAll();
    }
}
A Complete Ordering Process Test

Now we have implemented tests for all the little parts of our ordering process, but not for the interaction between these parts. Even though we already know that we calculate the shipping cost properly (well... let us say that we know how to write tests for all different cases), there is no warranty that the code handles it well and sets it to the order record.

In the attached code, you will find a long test case called "ShouldOrderCart()" that provides a full test for the ordering process. But it smells bad... It seems that if you make a mistake in the shipping cost calculation, then both the "ShouldSetNormalShippingCostForCustomersInBudapest()" and also the "ShouldOrderCart()" test cases will fail. Why did we implement both of them? Another problem is that if we have, for example, different calculations for Budapest and Vienna, then it is clear that we have to implement two test cases for that. Do we need two different versions of the "ShouldOrderCart()" as well? If there are alternative tests for the other sub-component, the possible variations for the "ShouldOrderCart()" grow exponentially. This is not what we want.

Actually we want to test only the interaction between these sub-components, which leads to a similar problem to the one we already encountered with the transactions. We can again introduce a new layer or use Rhino Mocks’ partial mocking features. The following example shows how you can test this with partial mocks. Please note that we do not have to specify any input parameter that is used by the sub-components only (such as the city), so it is clearly visible that we do not have the exponential variation problem.

[Test]
public void ShouldConnectSubComponents()
{
    // create mocks and record expectations

    // the MakeOrderLogic() method of the ordering service instance 
    // will be mocked, the rest remains the same
    OrderingService orderingServicePartialMock = 
        mockRepository.PartialMock<OrderingService>();

    // we do not need the proper shipping cost - we have tested the 
    // shiping cost calculation already
    decimal shippingCost = 42m;

    // similarly, we do not have to add items to the cart, but just ensure that the 
    // created order.Items collection returns 3 for the count
    int itemCount = 3;

    ShoppingCart cart = new ShoppingCart();

    Customer customer = mockRepository.Stub<Customer>();
    customer.EMail = "tired@sql";
    cart.Customer = customer;

    Order createdOrder = mockRepository.Stub<Order>();

    MockCollection<OrderItem> orderItemsMock = 
        mockRepository.PartialMock<MockCollection<OrderItem>>();
    SetupResult.For(createdOrder.Items).Return(orderItemsMock);

    // expectation: the created collection returns 3 for Count
    // instead of:
    // Expect.Call(orderItemsMock.MockCount).Return(itemCount);
    Expect.Call(orderItemsMock.MockCount).Return(itemCount);

    // expectation: the CalculateShippingCost is called with the customer 
    // of the cart and returns the same value (42)
    Expect.Call(orderingServicePartialMock.CalculateShippingCost(customer)).
        Return(shippingCost);

    // expectation: the CreateOrderTrunk is called with the customer 
    // of the cart and with the shipping cost returned by CalculateShippingCost, 
    // and returns the prepared order stub
    Expect.Call(orderingServicePartialMock.CreateOrderTrunk(customer, shippingCost)).
        Return(createdOrder);

    // expectation: the CreateOrderItems is called with 
    // the cart's items and the order stub
    orderingServicePartialMock.CreateOrderItems(cart.Items, createdOrder);

    // expectation: the SendConfirmationMail is called with 
    // the cart's customer's e-mail and the item count we return in the order stub
    orderingServicePartialMock.SendConfirmationMail(customer.EMail, itemCount);

    // end recording
    mockRepository.ReplayAll();

    // do the test
    orderingServicePartialMock.MakeOrderLogic(cart);

    // verification
    mockRepository.VerifyAll();
}
Stubbing and Mocking Queries

A typical scenario in data manipulating applications is that different queries have to be executed against the database. With applications that mainly manipulate data only, there is a lot of logic is implemented in these queries. And since these queries are evaluated on the database server, you cannot test them in a unit testing environment (without database) with the standard .NET unit testing methodologies.

Therefore the integration tests of the application should at least cover these queries; otherwise, a significant part of your application is left without automated tests. Of course, if you have to set up a reliable database integration test environment anyway, then the question of whether you should do all the tests (without the ones testing the failures) this way pops up again.

Testing the queries with database integration can be done in a "classicist" way: you set up the test data in the database, perform the operation and verify the result.

For these topics, we will concentrate on testing the code parts that use the results of the database queries, but will not go into the testing of the database queries themselves.

From a testing perspective, we have to distinguish between two types of these queries:

  • static queries: queries where the query structure does not depend on user input (although the parameters, and therefore the result of the query, can depend on the user input, of course). Most of the queries fall into this category.
  • dynamic queries: queries that are dynamically built based on user input. An example of this is a search form where the filter criteria of the query depend on the fields where the user has specified constraints (e.g. s/he is searching for name only, or for both name and company).

As the building of the static queries does not depend on user input, these are (or can be) well isolated from the business logic that uses the query result. For isolation, the same techniques that we mentioned earlier can be used. The code that produces the query result can be isolated by using a service interface and the business logic can obtain the interface implementation through dependency injection.

Since the queries are usually described using a specific query language that is outside the .NET world (SQL, OQL, etc.), these queries often specified in a declarative manner.

In Genome, these queries can be specified in the mapping file using OQL or LINQ, or even by specifying a stored procedure. If you specify the queries there, Genome can perform checks and pre-compilations at compile time.

The best way to do this (especially if you also want to have an easy-to-test environment) is to collect the methods that represent the database queries into interfaces (you can have more than one) that you map in the mapping file by using the <QueryProvider> mapping feature. You can read more about query providers here.

Unfortunately, we do not have a query in our ordering sample, so we will introduce a new reporting service into our application. We have defined the service in the following way:

    public interface IOrderReportingService
    {
        void ReportOrdersInYear(int year);
        void ReportOrders(int? year, string customerCity, string customerEMail);
    }
    

Both these methods should report some basic data about orders to the console.

To be able to test the reporting, we have immediately introduced an interface to isolate the Console.WriteLine() calls, so our initial implementation of the interface is as follows:

public class OrderReportingService : IOrderReportingService
{
    private DataDomain dataDomain;
    private IReportOutput reportOutput;

    public DataDomain DataDomain
    {
        get { return dataDomain; }
        set { dataDomain = value; }
    }

    public IReportOutput ReportOutput
    {
        get { return reportOutput; }
        set { reportOutput = value; }
    }

    private void DisplayOrders(Set<Order> orders)
    {
        foreach (Order order in orders)
        {
            reportOutput.DisplayOrder(order);
        }
    }

    public void ReportOrdersInYear(int year)
    {
        Set<Order> orders = dataDomain.Extent<Order>().
            Where("OrderDate.Year == {0}", year);
        DisplayOrders(orders);
    }

    public void ReportOrders(int? year, string customerCity, string customerEMail)
    {
        Set<Order> orders = dataDomain.Extent<Order>();
        if (year != null)
            orders = orders.Where("OrderDate.Year == {0}", year.Value);
        if (customerCity != null)
            orders = orders.Where("Customer.City == {0}", customerCity);
        if (customerEMail != null)
            orders = orders.Where("Customer.EMail == {0}", customerEMail);

        DisplayOrders(orders);
    }
}

Note: to keep the sample simple, we have omitted the transaction handling part from the implementation. In reality, you have to set up a read-only Genome context to perform these database operations.

Let us now consider the ReportOrdersInYear() method, which uses a static query that looks for the orders in a specific year in the database. In order to test the method (not the query!), we have to isolate the query building:

public interface IOrderQueries
{
    Set<Order> GetOrdersByYear(int year);
}

When using the <QueryProvider> mapping, you do not even have to implement this interface, as Genome can do it automatically. To get an instance of the generated implementation, the DataDomain.QueryProvider<IOrderQueries>() method has to be called.

To generate the query provider implementation, you have to specify in the mapping file how the methods should be translated to SQL:

<Type name="IOrderQueries">
    <QueryProvider />
    <Member name="GetOrdersByYear" signature="int"
        Oql="extentof(Order)[OrderDate.Year == year]" />
</Type>

Note: you can also map the method using LINQ or a stored procedure.

With this change, the implementation of our reporting service has been changed as well:

public class OrderReportingService : IOrderReportingService
{
    private IReportOutput reportOutput;
    private IOrderQueries orderQueries;

    public IOrderQueries OrderQueries
    {
        get { return orderQueries; }
        set { orderQueries = value; }
    }

    public void ReportOrdersInYear(int year)
    {
        Set<Order> orders = orderQueries.GetOrdersByYear(year);
        DisplayOrders(orders);
    }

    ...
}

When writing the test for the ReportOrdersInYear() method, we need to prepare the mock query result, and ensure that the mocked IOrderQueries interface returns these values.

For that, as with Collection<T>, we need a stub set implementation, as you cannot stub a collection directly with Rhino Mocks. An implementation of this set stub, which we will use now, can be found in the "TechTalk.Genome.Extensions.Testing" project.

[Test]
public void ShouldDisplayOrdersFromAYear()
{
    // create mocks and record expectations
    IReportOutput reportOutputMock = mockRepository.CreateMock<IReportOutput>();

    IOrderQueries orderQueriesMock = mockRepository.CreateMock<IOrderQueries>();

    Order order1 = mockRepository.Stub<Order>();
    Order order2 = mockRepository.Stub<Order>();
    Set<Order> orders = new SetStub<Order>(order1, order2);

    // expectation: the GetOrdersByYear() query should return our set stub for 2007
    Expect.Call(orderQueriesMock.GetOrdersByYear(2007)).Return(orders);

    // expectation: the CreateOrderItem should be called twice, and should return 
    // new order item stubs
    reportOutputMock.DisplayOrder(order1);
    reportOutputMock.DisplayOrder(order2);

    // end recording
    mockRepository.ReplayAll();

    // do the test
    OrderReportingService orderReportingService = new OrderReportingService();
    orderReportingService.ReportOutput = reportOutputMock;
    orderReportingService.OrderQueries = orderQueriesMock;

    orderReportingService.ReportOrdersInYear(2007);

    // verification
    mockRepository.VerifyAll();
}

As you can see, apart from the SetSub<T>, the test is very similar to the ones we did before. This is a "classicist" solution for the order set. You can also imagine a "mockist" way, however it is not that useful here. Applying expectations on calling a GetEnumerator() method... it will be a rare case when you need to do this. If you do need it, you can use the MockSet<T> class.

Testing Dynamic Query Building

Let us now investigate the other method in the OrderReportingService class, the ReportOrder(). Looking at the implementation, this is a basic example of dynamic query building. We cannot isolate the query to an external dependency, as producing the query is part of the business logic that we want to test.

This method is probably best tested with integration tests, but we will show a solution here that delegates the result back to static queries (one that represents all orders, one that can return orders for a year and two others that represent orders for a customer, city or e-mail address).

public void ReportOrders(int? year, string customerCity, string customerEMail)
{
    Set<Order> orders = orderQueries.GetAllOrders();
    if (year != null)
        orders *= orderQueries.GetOrdersByYear(year.Value);
    if (customerCity != null)
        orders *= orderQueries.GetOrdersByCustomerCity(customerCity);
    if (customerEMail != null)
        orders *= orderQueries.GetOrdersByCustomerEMail(customerEMail);

    DisplayOrders(orders);
}

The refactored code now combines these delegated results with an intersection, which is fortunately also supported by the SetStub<T> as well.

[Test]
public void ShouldDisplayOrdersByYearAndCustomerCity()
{
    // create mocks and record expectations
    IReportOutput reportOutputMock = mockRepository.CreateMock<IReportOutput>();

    IOrderQueries orderQueriesMock = mockRepository.CreateMock<IOrderQueries>();

    Order order1 = mockRepository.Stub<Order>();
    Order order2 = mockRepository.Stub<Order>();
    Order order3 = mockRepository.Stub<Order>();
    Set<Order> orders = new SetStub<Order>(order1, order2, order3);
    Set<Order> orders2007 = new SetStub<Order>(order1, order2);
    Set<Order> ordersLondon = new SetStub<Order>(order2, order3);

    // expectation: the GetAllOrders() query should return our set stub for all orders
    Expect.Call(orderQueriesMock.GetAllOrders()).Return(orders);

    // expectation: the GetOrdersByYear() query should return our set stub for 2007
    Expect.Call(orderQueriesMock.GetOrdersByYear(2007)).Return(orders2007);

    // expectation: the GetOrdersByCustomerCity() query should return our set stub for London
    Expect.Call(orderQueriesMock.GetOrdersByCustomerCity("London")).Return(ordersLondon);

    // expectation: the CreateOrderItem should be for order2 only, 
    // as it is from 2007 and London
    reportOutputMock.DisplayOrder(order2);

    // end recording
    mockRepository.ReplayAll();

    // do the test
    OrderReportingService orderReportingService = new OrderReportingService();
    orderReportingService.ReportOutput = reportOutputMock;
    orderReportingService.OrderQueries = orderQueriesMock;

    orderReportingService.ReportOrders(2007, "London", null);

    // verification
    mockRepository.VerifyAll();
}

All Green

With the WebShop sample walkthrough, I have tried to provide real life examples of writing unit tests against a business layer that uses an OR/M, particularly Genome, for data access. I have tried to focus on the problematic points that you can quickly run into if you decide to write unit tests for methods that are more difficult than adding two numbers. I am sure that I have not covered every nasty situation, but I hope that you can move on more easily in other situations by studying these solutions.

If you look at these unit tests in the sample code, you will see that they are relatively complex and not always easy to understand. My observation is that writing unit tests for complex logic is at least as complex as writing the code itself, if not more so. However, by learning the techniques and standard patterns of unit testing, you can improve your development of unit tests efficiently.

Well, do not forget that that there is a strong, proven correlation between the number of unit tests that are tested with green results and how well you can sleep on the day of subsequent deployments... May all your applications run green.

Quick Reference

Finally, here is a quick reference table of the problems we have investigated with the basic actions you need to take in order to solve them as well as some code snippets from the walkthrough that are relevant for these actions.

Problem
Action
Related code sample
Stubbing persistent classes set up Customer customer = mockRepository.Stub<Customer>();
customer.City = "Budapest";
verify Assert.AreEqual(10m, order.ShippingCost);
Mocking object lifetime actions refactor public interface IOrderingServiceDataAccess {
Order CreateOrder();
}
set up IOrderingServiceDataAccess dataAccessMock =
mockRepository.CreateMock<IOrderingServiceDataAccess>();
expectation Expect.Call(dataAccessMock.CreateOrder()).
Return(mockRepository.Stub<Order>());
verify mockRepository.VerifyAll();
Stubbing collections set up SetupResult.For(order.Items).Return(new CollectionStub<OrderItem>());
verify Assert.AreEqual(2, order.Items.Count);
Assert.AreEqual(3, order.Items[0].Quantity);
Mocking collections set up MockCollection<OrderItem> orderItemsMock =
mockRepository.PartialMock<MockCollection<OrderItem>>();
SetupResult.For(order.Items).Return(orderItemsMock);
expectation orderItemsMock.Add(orderItem1);
expectation Expect.Call(orderItemsMock.MockCount).Return(42);
verify mockRepository.VerifyAll();
Testing transactional behaviour refactor public interface IOrderingServiceDataAccessy
{
   IDisposable PushUpdateContext();
   void CommitChanges();
}
refactor public void MakeOrder(ShoppingCart cart)
{
   using(dataAccess.PushUpdateContext())
   {
      MakeOrderLogic(cart);
      dataAccess.CommitChanges();
   }
}
Stubbing static queries refactor public interface IOrderQueries
{
   Set<Order> GetOrdersByYear(int year);
}
map <Type name="IOrderQueries">
   <QueryProvider />
   <Member name="GetOrdersByYear" signature="int"
      Oql="extentof(Order)[OrderDate.Year == year]" />
</Type>
set up Set<Order> orders = new SetStub<Order>(order1, order2);
Expect.Call(orderQueriesMock.GetOrdersByYear(2007)).Return(orders);
Mocking static queries refactor public interface IOrderQueries
{
   Set<Order> GetOrdersByYear(int year);
}
set up MockSet<Order> ordersMock =
   mockRepository.PartialMock<MockSet<Order>>();
expectation Expect.Call(ordersMock.MockCount).Return(42);
verify mockRepository.VerifyAll();

You can download the sample code of this article from below. Note that you need Genome 3.3 (beta 1 is fine) to try out the sample.

Shop.zip

Posted by Gáspár

Technorati Tags:

Comments are closed.