Want to show your appreciation? Please to my charity.

Wednesday, April 28, 2010

SpringExtension: Declarative Transaction Management and Unit Test

Declarative transaction management is one of the compelling reasons to using Spring.Net. But there are still cases where you need to programmatically rollback a transaction without throwing an exception. Spring.Net let you do so with below method call:

TransactionInterceptor.CurrentTransactionStatus.SetRollbackOnly();

The problem with this call emerges when you unit test your business objects containing such statement. You got an exception when you run your test because there is no transaction context exists.

The solution to this problem is dependency injection. We need to introduce an interface that can give us the transaction status.

    public interface ITransactionStatusProvider
    {
        ITransactionStatus CurrentTransactionStatus { get; }
    }

We can then use the interface to rollback current transaction in our business objects:

   1:      public class BusinessObject
   2:      {
   3:          ITransactionStatusProvider TransactionStatusProvider { get; set; }
   4:          public void BusinessMethod()
   5:          {
   6:              // Do some work.
   7:              if (something is wrong)
   8:              {
   9:                  TransactionStatusProvider.CurrentTransactionStatus.SetRollbackOnly();
  10:              }
  11:          }
  12:      }

In line 3, we declared a property so the provider can be injected; In line 9, we used the provider to rollback the transaction in error condition.

Since both provider and status are interfaces, we can easily mock them in our unit test:

   1:          [Test] public void BusinessMethodRollBackTransactionWhenSomethingIsWrong()
   2:          {
   3:              var mockProvider = MockRepository.GenerateMock<ITransactionStatusProvider>();
   4:              var mockStatus = MockRepository.GenerateMock<ITransactionStatus>();
   5:              mockProvider.Stub(p => p.CurrentTransactionStatus).Return(mockStatus);
   6:   
   7:              var sut = new BusinessObject {TransactionStatusProvider = mockProvider};
   8:              // sut.SetUpWrongCondition
   9:              sut.BusinessMethod();
  10:   
  11:              mockStatus.AssertWasCalled(x=>x.SetRollbackOnly());
  12:          }

The last thing left is to write a real implementation that actually works with the Spring.Net’s declarative transaction management:

    public class TransactionInterceptorStatusProvider : ITransactionStatusProvider
    {
        public ITransactionStatus CurrentTransactionStatus
        {
            get { return TransactionInterceptor.CurrentTransactionStatus; }
        }
    }

We then use Spring.Net’s dependency inject feature to set the the BusinessObject.TransactionStatusProvider to an instance of TransactionInterceptorStatusProvider. Problem resolved!

This is part of SpringExtension, both source and binary are available on Google Code.

No comments:

Post a Comment