Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested TransactionScope fails in tests

I'm using MSTest to run some automated tests against a MySQL 5.5.19 DB via the MySQL Connector and using EntityFramework 4.3.

I'm attempting to use TransactionScope in my DB-accessing class library to perform a rollback when needed. Additionally, in my test code I wish to use TransactionScope to put the DB back into a known state before each test. I use the TestInitialize and TestCleanup methods to accomplish this. Those look like so:

[TestInitialize()]
public void MyTestInitialize()
{
   testTransScope = new TransactionScope(TransactionScopeOption.RequiresNew);
}

[TestCleanup()]
public void MyTestCleanup()
{
   Transaction.Current.Rollback();
   testTransScope.Dispose();
}

Based on the construction of the TransactionScope object there in the initialize function, I believe I should be getting a new transaction scope (there isn't an "ambient" one existing so I believe this ".RequiresNew" isn't technically important here since ".Required" would produce the same result. Since I don't specify a timeout value, it provides me with the default timeout, which I understand to be 60 seconds. Plenty of time for my given test to run.

I've got a function called AddDessert(DessertBiz dessertBizObject) which looks, in part, something like this:

using (var transScope = new TransactionScope(TransactionScopeOption.Required))
{
   try
   {
      // ...
      context.Desserts.Add(dessert);
      context.SaveChanges();
      var dessertId = dessert.Id;
      DoOtherDessertStuff(dessertId, dessertBizObject);
      transScope.Complete();
   }
   catch (InvalidOperationException ex)
   {
      Console.WriteLine(ex.ToString());
   }
}

And this function is called by one of my tests.

Since I've specified TransactionScopeOption.Required here, I expect that it will use the "ambient" transaction scope created by the MyTestInitialize function.

My test is arranged to make this DoOtherDessertStuff function to fail and throw an exception, so the call to transScope.Complete(); doesn't happen and the rollback occurs automatically when exiting the using block in the AddDessert function.

The problem I have here is that since it uses the ambient transaction scope created in the MyTestInitialize function, my test Assert calls don't happen because the transaction scope rollback happened - at least this is what I think is happening. I verified that Transaction.Current.TransactionInformation.Statusis TransactionStatus.Aborted, so I feel pretty sure this is what's happening.

Great, so I thought I would change my AddDesert method to look exactly as above except that I would nest a transaction scope rather than using the ambient one, some my using line looks looks like this:

using (var transScope = new TransactionScope(TransactionScopeOption.RequiresNew))

The intent here was that I could nest these transaction scopes, let the rollback in my production code occur and then still check my Asserts in my test code.

But what I am finding is that I get the following error:

System.IO.IOException: Unable to read data from the transport connection: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host failed to respond.

Ideas?

like image 674
itsmatt Avatar asked Apr 13 '12 17:04

itsmatt


1 Answers

Very good question. When you reference datacontext inside your testmethod after rollback, it will not be available. You need to suppress that. You don't need to specify required option. It is the default option.

Test Method:

  [TestMethod()]
    public void CreateTestCheckContextCorrectly()
    {
        MailJobController target = new MailJobController();

        target.AddDessert("dessert for Omer");
        //With suppress, even if you rollback ambient trans, suppress will ignore ambient trans. You need to reference new context, previous context from controller may be disposed.
        using (var suppressscope = new TransactionScope(TransactionScopeOption.Suppress))
        {
            var newdbcontextref = new DbEntities();

            int recordcount = newdbcontextref.StatusDefinitions.Where(x => x.Name == "dessert for Omer").Count();

            Assert.AreEqual(0, recordcount);
        }
    }

Controller method:

 public void AddDessert(string dessert)
   {
       using (var transScope = new TransactionScope())
       {
           try
           {
               // ...
               StatusDefinition statusDefinition = new StatusDefinition() {Name = dessert};
               db.StatusDefinitions.AddObject(statusDefinition);
               db.SaveChanges();
               Console.WriteLine("object id:"+statusDefinition.StatusDefinitionId);
               throw new Exception("hee hee");
               transScope.Complete();
           }
           catch (Exception ex)
           {
               Console.WriteLine(ex.ToString());
           }
       }
   }
like image 173
Omer Cansizoglu Avatar answered Sep 27 '22 17:09

Omer Cansizoglu