Use Transactions and Rollback for Unit Testing against Entity Framework


Refer to my previous post that talked about Unit Test with Rollback Pattern, Entity Framework is one of ORM tool that developer has to learn it to use accurately. So if you use Mock to do such a by pass of querying against Entity Framework, it is pretty unsafe really. Except you are very confident that you (or your developer) are mastery of Entity Framework.

First of all you need to have a separate database for test only and prepare some data for testing purpose. Then change the connection string in config file to point to this database rather than the development database.

Here is how I wrap and override the object context.

1. Create a new class that override DBTransaction class.

  • Make the constructor to accept a parameter of DbTransaction object. So this class acts just like an adapter of DbTransaction class.
  • By pass the real rollback and commit behavior by overriding Rollback and Commit functions so actual rollback and commit functions will not do anything.
  • Create RollbackUnitTest function to be called from another class that we are going to write it up next.
public class TestDbTransaction : System.Data.Common.DbTransaction
{
   public DbTransaction ActualDBTransaction { get; private set; }

   public TestDbTransaction(DbTransaction actualDBTransaction)
   {
      ActualDBTransaction = actualDBTransaction;
   }

   public override void Rollback()
   {
      // do nothing : the test transaction is already running
   }

   public override void Commit()
   {
      // do nothing : the test transaction is already running
   }

   public void RollbackUnitTest()
   {
      ActualDBTransaction.Rollback();
   }

   protected override System.Data.Common.DbConnection DbConnection
   {
      get { return ActualDBTransaction.Connection; }
   }

   public override IsolationLevel IsolationLevel
   {
      get { return ActualDBTransaction.IsolationLevel; }
   }
}


2.  Create extension methods of ObjectContext named BeginTransaction to begin the transaction and return TestDbTransaction object out.

  public static class TestObjectContextExtender
  {
        public static System.Data.Common.DbTransaction BeginTransaction(this System.Data.Objects.ObjectContext context, IsolationLevel isolationLevel)
        {
            var actualTransaction = context.Connection.BeginTransaction(isolationLevel);
            return new TestDbTransaction(actualTransaction);
        }

        public static System.Data.Common.DbTransaction BeginTransaction(this System.Data.Objects.ObjectContext context)
        {
            return new TestDbTransaction(context.Connection.BeginTransaction());
        }
  }

3. Create a base class to be inherited by each unit test class.

TestStart method is being called before every unit test and TestEnd after. This is the result of pasting attribute TestInitialize and TestCleanup on them.

    [TestClass()]
    public class TransactedTestClass
    {
        protected DBEntities _dataRollbackContext;
        private TestDbTransaction _transaction;

        [TestInitialize()]
        public void TestStart()
        {
            _dataRollbackContext = new DBEntities();
            _dataRollbackContext.Connection.Open();
            _transaction = _dataRollbackContext.BeginTransaction() as TestDbTransaction;
        }

        [TestCleanup()]
        public void TestEnd()
        {
            _transaction.RollbackUnitTest();
        }
    }

4. Sample of use. Create a test class and inherit from TransactedTestClass.

[TestClass()]
public class DataRepositoryTest : TransactedTestClass
{
        [TestMethod()]
        public void AddNewUser_Test()
        {
            // In this example I made up the data repository and use dependency injection for the data context.
            DataRepository target = new DataRepository(_dataRollbackContext);
            // Assume that our test database has no record in Users table
            Assert.AreEqual(0, _dataRollbackContext.Users.Count());
            target.AddNewUser(new User());
            // Verify that one record is added
            Assert.AreEqual(1, _dataRollbackContext.Users.Count());
        }
}

There are other approaches that expect the same result.

  • One is wrapping up each unit test with usage of TransactionScope without commit statement. This ensure that any change to the data in database will be rollback when the unit test is done.  Even though, this seems to be easy and straightforward but because of Entity Framework basically begins transaction automatically and commits once SaveChanges method is being called. Begin transaction within another transaction requires MSDTC. Thus, MSDTC must be enabled and this is not make any sense.
  • Second is doing a database snapshot and restore every time the test completed. This approach is fairly complicated and difficult to manage.
Advertisements
Tagged , , , , , , , , ,

2 thoughts on “Use Transactions and Rollback for Unit Testing against Entity Framework

  1. […] approach that I’m going to show you is pretty much the same as my previous post “Use Transactions and Rollback for Unit Testing against Entity Framework“, because as I mentioned that RIA uses Entity Framework as ORM […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: