Integration Tests with NHibernate

4 commentsWritten on June 22nd, 2007 by
Categories: NHibernate, Software Development, testing

In my last post i experimented with a way to run my integration tests without polluting my database. I created a specific TransactionFactory which NHibernate would use to create transactions whenever they were requested. My factory returned a transaction which would actually rollback the transaction, instead of committing it. This way, the transactions were not committed during test runs, and the code being tested did not have to be changed. It works, but it gets messy very quickly if an integration test would call multiple methods that were transactional. The test transaction would have to stay alive during the entire test, and would have to be reused by all the transactional methods called in that test, all without changing the code being tested. It's doable, but i didn't feel too good about it.

I looked for other options and i believe i've found a pretty good one. It would be great if i could somehow wrap every data access call caused by a test into one MS DTC transaction. Then, after the test i'd simply have to abort the MS DTC transaction. The database stays clean, the tests remain independent of what may already be in the database and most importantly, i wouldn't have to change my production code.

So, what do i need here? First i need a way to connect an NHibernate session to the current DTC transaction. And i also need a custom TransactionFactory for NHibernate to use which will return dummy transaction objects which do nothing. The production code will receive these transaction dummies whenever a transaction is requested and will call Commit() on them when needed. Obviously, the Commit() implementation of my dummy transaction is a no-op.

Let's start with connecting an NHibernate session to a DTC transaction. NHibernate provides access to the underlying database connection through the ISession.Connection property which returns an IDbConnection. Most objects that implement IDbConnection have an EnlistDistributedTransaction() method, which does exactly what we want, but it's not defined in the IDbConnection interface. The only one that doesn't have this method as far as i know, is the implementation in the System.Data.SqlServerCe namespace. I don't use SqlServerCe so that's not a problem for me.

I want my production code to request NHibernate sessions through my own provider which implements the following interface:

public interface ISessionProvider
{
  public ISession GetNewSession();
}

At runtime, the real SessionProvider will be used which will provide NHibernate sessions which use the default AdoNetTransactionFactory and AdoTransactions. At unit-test time, the following ISessionProvider will be used:

public class SessionUsingDtcProvider : ISessionProvider
{
  private ISessionFactory _sessionFactory = null;

  private ISessionFactory SessionFactory
  {
    get
    {
      if (_sessionFactory == null)
      {
        NHibernate.Cfg.Configuration configuration =
           new NHibernate.Cfg.Configuration()
              .AddAssembly("MyMappingAssembly");

        ISessionFactory sessionFactory =
           configuration.BuildSessionFactory();

        // overwrite the NHibernate TransactionFactory so it will use our
        // own factory. check the previous post if you don't know why
        // i do it this way
        PropertyInfo propertyInfo = sessionFactory.Settings.GetType()
            .GetProperty("TransactionFactory");
        propertyInfo.SetValue(sessionFactory.Settings,
             new TestTransactionFactory(), null);
      }

      return _sessionFactory;
    }
  }

  public ISession GetNewSession()
  {
    ISession session = SessionFactory.OpenSession();
    EnlistInDtcTransaction(session.Connection);
    return session;
  }

  private void EnlistInDtcTransaction(IDbConnection connection)
  {
    MethodInfo methodInfo = connection.GetType()
       .GetMethod("EnlistDistributedTransaction",
          BindingFlags.Public | BindingFlags.Instance);

    methodInfo.Invoke(connection, new object[]
      { (System.EnterpriseServices.ITransaction)
           ContextUtil.Transaction
      });
  }
}

So the EnlistDistributedTransaction() method is called through reflection because it's not defined in the interface. I wouldn't do this for production code, but for test-code i think it's OK.

The TestTransactionFactory class looks like this:

public class TestTransactionFactory : ITransactionFactory
{
  public void Configure(System.Collections.IDictionary props)
  {
  }

  public ITransaction CreateTransaction(ISessionImplementor session)
  {
    return new TestTransaction();
  }
}

And the TestTransaction is as dumb as it possibly could be:

public class TestTransaction : ITransaction
{
  public void Begin(System.Data.IsolationLevel isolationLevel)
  {
  }

  public void Begin()
  {
  }

  public void Commit()
  {
  }

  public void Enlist(System.Data.IDbCommand command)
  {
  }

  public bool IsActive
  {
    get { return true; }
  }

  public void Rollback()
  {
  }

  public bool WasCommitted
  {
    get { return false; }
  }

  public bool WasRolledBack
  {
    get { return false; }
  }

  public void Dispose()
  {
  }
}

Now suppose you have the following code in your business layer:

public class CustomerService
{
  private ISessionProvider _sessionProvider;

  public CustomerService(ISessionProvider sessionProvider)
  {
    _sessionProvider = sessionProvider;
  }

  public void SaveCustomer(Customer customer)
  {
    using (ISession session = _sessionProvider.GetNewSession())
    using (ITransaction transaction = session.BeginTransaction())
    {
      session.SaveOrUpdate(customer);
      session.Flush();
      transaction.Commit();
    }
  }
}

As you can see, this code requests a transaction and commits the transaction after the data is inserted or updated in the database. At runtime, this is exactly what i want. But during my tests, i don't want this to actually be commited.

So now i could test this without polluting my database like this:

[TestClass]
public class MyTestClass
{
  [TestInitialize]
  public void TestInitialize()
  {
    SetUpDtcTransaction();
  }

  private void SetUpDtcTransaction()
  {
    ServiceConfig serviceConfig = new ServiceConfig();
    serviceConfig.Transaction = TransactionOption.RequiresNew;
    ServiceDomain.Enter(serviceConfig);
    ContextUtil.MyTransactionVote = TransactionVote.Commit;
  }

  [TestCleanup]
  public void TestCleanup()
  {
    AbortDtcTransaction();
  }

  private void AbortDtcTransaction()
  {
    ContextUtil.MyTransactionVote = TransactionVote.Abort;
    ServiceDomain.Leave();
  }

  [TestMethod]
  public void TestSaveCustomer()
  {
    CustomerService service =
       new CustomerService(new SessionUsingDtcProvider());

    Customer customer = new Customer();
    customer.Name = "Davy Brion";

    service.SaveCustomer(customer);

    Assert.IsTrue(customer.Id > 0);
  }
}

Mission accomplished :)

Note: this test instantiates the SessionUsingDtcProvider which will create the NHibernate SessionFactory which is a rather expensive operation... In your production code, the correct SessionProvider should only be created once and made available so the rest of the code has easy access to it.

Update: it's no longer necessary to set NHibernate's TransactionFactory through reflection. Read this...

  • Peter Bauwens

    Davy,

    While reading your post I wondered if this approach would work for all scenario’s. What happens if the test you wrote should check if a certain operation (e.g. save) failed and other actions already performed on the database (e.g. save of related records) are rolled back. I suppose the dummy TestTransaction would neglect to really rollback these changes hence the faulty records would still be in the database when you perform any checks against it? Am I right or am I overlooking something?

    Nice post by the way….

  • http://www.ralinx.be Davy Brion

    Yes, you’re right. This approach is definitely not good for all scenarios. To me, the kind of tests you mention should be lower level tests… tests that extensively test a specific unit.

    The test in this post is actually a bad example of what i consider an integration test as it is more of a unit test of the CustomerService. This approach is more suited if you want to test the entire flow of your application, from the presentation layer to the business layer and back again. There’s no need to test every possibility in these kind of tests because every possibility should be covered by lower level unit tests. The integration tests i’m talking about just need to make sure everything is working together properly.

    For instance, you mention checking if faulty records are still in the database if a service call failed. This specific activity should be covered by a unit test. However, an integration test would need to check if the correct business exception is received in the presentation layer.

    Thx for the comment ;)

  • http://www.springframework.net Mark Pollack

    Hi,
    You maybe interested to use the integraiton test features of Spring.NET so that a rollback will occur after each of test methods. It is similar to your approach at heart. At the moment this requires inheriting from a base class for NUnit but eventually will be attribute based given the extensibility in NUnit 2.4. The docs are here http://www.springframework.net/doc-latest/reference/html/testing.html

    Feel free to post questions in the forums.

    Cheers,
    Mark

  • http://ralinx.wordpress.com Davy Brion

    Hi Mark,

    I haven’t used Spring.NET yet, but what i’ve seen from it looks very nice. I’ll definitely play around with it soon :)