TDD and Commerce Server 2007: Getting Started

January 22nd, 20090 comments

Mixing Commerce Server 2007 with unit testing frameworks is a little tricky. The few examples I’ve seen use MsTest specific features to route tests through ASP.NET or IIS when executing, providing the following drawbacks:

  1. Increased test execution time (sucks worse when running on a VPC)
  2. Increased cost for creating tests

Techniques like dependency injection can be used to significantly reduce the number of tests that require a web host for execution. I’m using the Orders subsystem here, but the same techniques can be used with other subsystems as well.

Example Code

For this example I unpacked the CSharp.pup file to create a new site named CommerceSample. You can download the example code here.

Breaking Dependencies

Commerce Server is tightly coupled to ASP.NET and this causes problems when trying to execute Commerce Server API code outside of a web application. Suppose we try to execute the following code within a unit testing framework:

var userId = Guid.NewGuid();
var basket = OrderContext.Current.GetBasket(userId);

This produces a NullReferenceException due to an uninitialized singleton, OrderContext.Current. In a web application scenario this context is initialized by the HttpModule CommerceOrderModule.

Establishing a Test Context

To execute code like this within a testing framework, we need to create an instance of OrderContext. One approach is to create a custom base class for tests requiring the use of an OrderContext, as shown below:

public class CommerceFixture {
    public const string SiteName = "CommerceSample";

    public static readonly OrderContext TestOrderContext =
        OrderContext.Create(SiteName);
}

If I have a test that requires an OrderContext, my test class can now extend this class and have access to the TestOrderContext instance. One thing to point out: OrderContext.Create will also initialize OrderContext.Current, so technically you don’t need to store the reference in an accessible property.

Wrapping and Injecting Context Objects

When working with context objects, I like to create wrappper types and inject the context into the wrapper. This allows for the following benefits:

  1. The ability to add new behavior to context objects (the usual benefit of wrapper classes)
  2. No dependence on singletons (a lot of material has been written about this already)
  3. Narrowing the API. Sometimes context objects can be a dumping ground for anything and everything. Wrapper classes provide a nice way to offer a subset of this functionality specific to my application.

Rolling Back Updates

If a test updates a database, we want to make sure the updates are removed upon test completion, preventing unwanted side effects. Most mature testing frameworks provide some form of a rollback attribute, which wraps each test in a TransactionScope instance (if you’re stuck with MsTest, you can wrap your test code in a using TransactionScope block).

Note: Originally I was told using a TransactionScope might not work with Commerce Server API’s due to legacy COM code, so I’ve actually never used this during full project development. I didn’t realize this worked until playing with this example while creating this post. One context type it might not work with is the ProfileContext. That said, there’s more than one way to rollback changes. I’ll discuss the rollback approach I’ve used before in a subsequent post.

An Example

Lets polish the recurring order example from MSDN. I’d like to have a method that accepts a user id and a template name (basket name) and returns a basket that is already populated with items from the saved template:

var cartService = new ShoppingCartService(orderContext);
var cart = cartService.CreateFromRecurringOrder(userId, templateName);

ShoppingCartService is an example of a thin wrapper, where the context is injected in.

To setup a test I need to create a recurring order template in the database, so my service class can find it. This is the update I want rolled back upon test completion. My final test looks like this:

[Fact, AutoRollback]
public void Should_create_new_order_from_recurring_order_for_user() {
    var userId = Guid.NewGuid();
    var templateName = "MonthlyOrder";
    var cartService = new ShoppingCartService(TestOrderContext);

    var recurringOrder = buildRecurringOrder(cartService, userId, templateName);
    var cart = cartService.CreateFromRecurringOrder(userId, templateName);

    Assert.Equal(recurringOrder.LineItems.Count, cart.LineItems.Count);
}

Where buildRecurringOrder is a helper function that creates an order template in the database. For this test I used xUnit.net. When the test completes, the order I saved will no longer exist. You can use the Customer and Orders Manager to verify that your tests are not leaking baskets and orders.

Future Topics

In subsequent posts I’ll discuss the following topics:

  1. Using custom scope objects for situations where TransactionScope might not work
  2. How to handle pipeline execution with testing frameworks
  3. Testing with Commerce Server 2009

Related Information

  1. Dependency Injection
  2. Misko Hevery discussing hard to test third party code
  3. Context object pain. See Misko here
  4. Singleton pain. See Misko again

Posted in CommerceServer,TDD