Wednesday, January 25, 2006

FitNesse: Using COM+ Enterprise Services to Manage Transactions

I’ve been working on integrating FitNesse and FIT into my current project. As with all automated testing, managing test data is a challenge.

To manage the data we have 3 options:

  1. Abstract the data from the system
  2. Restore the data to a known state before the tests run
  3. Create a reciprocating transaction. i.e. Each insert has a corresponding delete
  4. Rollback the test when it’s completed.

Option 1 is great, but it is difficult to abstract a system from the database especially if you are working with a pre-existing (legacy) system.

Option 2 is viable, but I hate having to do a database restore before I run a test suite.

I’m too lazy for option 3 and wouldn’t pick this option if there are a slew of user acceptance tests(UAT).

So I want to use option 4. Roy Osherove wrote a great article describing how to manage your unit tests using COM+ enterprise services and I have been using this method ever since, however, my tests use “Services Without Components” (SWC) included in COM+ 1.5.

I figured I could you the same concept with FitNesse! My solution is very simple. I created a ColumnFixture that can begin a transaction, commit a transaction, but most importantly rollback a transaction. The Fixture has 2 columns, Action and Result?. The Action column take one of three values: “begin”, “rollback”, and “commit”. The Result? column can be left blank. It will get populated with the System.EnterpriseServices.TransactionStatus after the Result() method is executed.

Now that I have this ColumnFixture, I created two pages: BeginTransaction and RollbackTransaction. These pages will be included before and after, any test fixtures that I want to run within a transaction. I prefer to use FitNesse’s !include widget, but you could also use the SetUp and TearDown pages.

Here's the code.

/// <summary>
    /// This class provides a simple interface to support COM+ transactions.
    /// </summary>
    public class TransactionFixture : ColumnFixture
    {
        public string Action = "begin";
        public string Result()
        {
            TransactionStatus retVal = TransactionStatus.NoTransaction;
            switch(Action.ToLower())
            {
                case "begin":
                    ServiceConfig config = new ServiceConfig();
                    config.Transaction = TransactionOption.RequiresNew;
                    ServiceDomain.Enter( config );
                    break;
                case "rollback":
                    
                    if( ContextUtil.IsInTransaction )
                    {
                        ContextUtil.SetAbort();
                    }
                    retVal = ServiceDomain.Leave();
                    break;
                case "commit":
                    
                    if( ContextUtil.IsInTransaction )
                    {
                        ContextUtil.SetComplete();
                    }
                    retVal = ServiceDomain.Leave();
                    break;
                default:
                    throw new ArgumentException("Invalid value", "Action");
            }

            return retVal.ToString();
        }
    }

Screenshot - Before


Screenshot - After


 

BeginTransaction Page

|!-GiffordConsulting.FitNesseAid.TransactionFixture-!|
|Action|Result?|
|begin||

RollbackTransaction Page

|!-GiffordConsulting.FitNesseAid.TransactionFixture-!|
|Action|Result?|
|rollback||

Putting it all together using import widget

!include BeginTransaction

|!-GiffordConsulting.FitNesseAid.SignonFixture-!|
|Username|Password|Save?|
|Tim|apassword|true|

!include RollbackTransaction