System.Transactions : Unit Testing

In my previous post, I gave a small introduction into System.Transactions and the TransactionScope object. You might already know about it, but I noticed a lot of people still don’t, while they really should. It’s really easy to use and understand, and can solve a lot of problems.
 
For example, how about using it for your unit tests? While your tests should test small units, it’s also wise to have integration tests, and see if your application is still accessing the database correctly. Common practice is to do this on a daily build server, where you setup the database before you start the test-run.
 
Another practice is that you cannot assume that your tests run serially. If that was the case, you’d insert a user into some table, and in the second test use that same user. But tests can run parallel or in a different sequence. So it’s wiser to setup some method that will insert a user, and have one test check if that’s working. We’ll call that InsertUserTest. Another test can than use the method to insert a user, and see if it can update that user. We’ll cat this one UpdateUserTest.
 
When the InsertUserTest breaks, the UpdateUserTest will automatically break also. But you can then track it down pretty fast to the InsertUserTest test, fix that one, and the UpdateUserTest will probably also run.
 
Now transactions come into scope, because at the end of every test, you want that inserted user to be rolled back. This is done very easily with the TransactionScope object. First, let’s look at the InsertUser method. This method must run in our transaction, but the method itself doesn’t have to know about it. We don’t have to close the connection, because the using statement will take care of it, as it calls the Dispose() method on the SqlConnection.
 

/// <summary>

/// Inserts a user

/// </summary>

/// <returns>Returns the primary key value</returns>

private int InsertUser()

{

  int identityValue = 0;

 

  using (SqlConnection conn = new SqlConnection())

  {

    conn.Open();

    // Insert user, get the primary key value

    // for the example, we’ll set it here:

    identityValue = 1;

  }

  return identityValue;

}

 
Next is the insert test, which calls the InsertUser. If the above method throws an exception, or the insert fails, the Assert in this method will also fail. Because the call to InsertUser lies within the TransactionScope block, the inserted user will be rolled back.
 

[Test]

public void InsertUserTest()

{

  int identityValue = -1;

  using (TransactionScope scope = new TransactionScope())

  {

    identityValue = InsertUser();

  }

  Assert.AreNotEqual(-1, identityValue);

}

 
The last one is the update test. You can see it also calls the top InsertUser method, but also tries to update the user. Both will fall under the same transaction, and both will be rolled back, because we don’t call the scope.Complete() method.
 

[Test]

public void UpdateUserTest()

{

  int identityValue = -1;

  int rowsUpdated = 0;

 

  using (TransactionScope scope = new TransactionScope())

  {

    identityValue = InsertUser();

 

    using (SqlConnection conn = new SqlConnection())

    {

      conn.Open();

      // update user, using the retrieve pk value.

      // set rowsUpdated; again, for the example, we’ll set it here:

      rowsUpdated = 1;

      // Do NOT call scope.Complete()

      // The transaction will rollback.

    }

  }

  Assert.AreNotEqual(-1, identityValue);

  Assert.AreEqual(1, rowsUpdated);

}

 
This shows how easy it is to unit test your application, without having to worry a bit about the data inserted.
 
Table of Contents
  1. An introduction
  2. Unit Testing

You may also like...

4 Responses

  1. That’s a very interesting capability, but, this is integration testing. You are making an out-of-process call to the database. For these tests to pass, you’d have to ensure your environment is set up every time.

    Unit tests don’t call out to environmental dependencies.

  2. Hi Jeffrey, you’re absolutely right, that’s why I mentioned it in my post

    “it’s also wise to have integration tests, and see if your application is still accessing the database correctly. Common practice is to do this on a daily build server”

    Perhaps I could’ve chosen my title a little wiser. But in my experience, people don’t really know the Agile/TDD principles about unit testing and testing small units. Or care, for that matter. πŸ™‚

  3. Jack Jiang says:

    I am testing a web service for inserting into database, how is transactionscope going to come in place, is it going to rollback the database after the insert or not?

    Thanks

  4. @Jack: It depends on what you’re doing. Are you starting the transaction inside the unit test or inside the webservice.

    Inside the webservice means you’ll have to tell your webservice to rollback the transaction

    Inside your unit test is a different thing. If you’re using .ASMX webservices, there’s no change the transaction will flow over the webservice. Even with WSE 3.0 it’s impossible.

    If you’re using WCF services it is possible, but you’ll have to alter the design of your service if it doesn’t already support flowing transactions into it. And transactions aren’t always a good design for your services.

    Normally for these kind of integration tests you should setup a specific test database. Just before running the scripts you restore or create that database with a fixed set of test-data in it. You run the tests and assert the data that’s returned. On the next test-run, you again restore the database. That’s a much better practice for these kind of tests then using transactions. πŸ™‚

    If you want more info, just reply here or contact me personally.

Click on a tab to select how you'd like to leave your comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.