EF4 Code First, TDD, CRUD, and Transactions

2019-03-22 04:14发布

In the past, I've written unit tests for simple CRUD operations when creating data access/repository code that look something like this:

using(var connection = new WhateverConnection(connectionString))
{
    connection.Open();
    using(var transaction = connection.BeginTransaction())
    {
        try
        {
            //test the CRUD operation
        }
        finally
        {
            //gets rid of any stuff created during the test
            transaction.Rollback();
        }
    }
}

I was messing around with EF4 Code First today, and I realized that I have no idea how this testing scenario translates in the Entity Framework lexicon. It seems that, if I call DbContext.SaveChanges(), it saves and commits, regardless of whether or not AcceptAllChanges() was called. Even using ObjectContext instead of DbContext, I can't figure out how this simple test scenario can be recreated without manually cleaning up any mock/test objects created. I did read this article on MSDN, but TransactionScope doesn't really have a Rollback type method either. Do I use TransactionScope and never call Complete? Is there another method or manner of using DbContext and/or ObjectContext in order to Rollback during unit tests? Do I need to completely re-adjust my thinking for TDD with EF4 Code First?

1条回答
叛逆
2楼-- · 2019-03-22 04:42

ObjectContext itself does not expose transactional behavior. You have to wrap EF code in transaction by yourself. The easy way to do it is using TransactionScope. If you don't call Complete method on the scope and dispose it, it will perform rollback. We are usually using base class for this type of integration tests:

[TestClass]
public abstract class TransactionTestBase
{
    private TransactionScope scope = null;

    [TestInitialize]
    public virtual void TestInitialize()
    {
      scope = new TransactionScope(TransactionScopeOption.RequiresNew,
          new TransactionOptions()
              {
                  IsolationLevel = IsolationLevel.ReadUncommitted
              });
    }

    [TestCleanup]
    public virtual void TestCleanup()
    {
        if (scope != null)
        {
            scope.Dispose();
            scope = null;
        }
    }
}

All test classes derive from this class. TestInitialize is called before each TestMethod in derived class and TestCleanup is called after each TestMethod so your tests don't have to deal with transaction at all.

查看更多
登录 后发表回答