In my old post I explained how you can create a NUnit add-in for reverting changes in database made by unit tests. In this add-in I use TransactionScope class to open a MSDTC transaction and rollback changes made by the unit test. I found this approach time consuming and unpredictable (sometimes my tests doesn’t work with an exception connected with DTC connection). I wasn’t able to find why it crashes.
Today, I’d like to write about two classes I have created in order to support the same functionality but without extensions and without TransactionScope class.
First of all let me say that I use NHibernate in the project for accessing DB and .Net Framework 3.5 for application development.
Firstly I’ve created a helper class which accepts a session of type ISession and provides a single method Execute. Execute method accepts an instance of Action delegate and invokes the action into a transaction which is rallbacked. This way all changes made in database by the test will be rollbacked.
public class RollbackHelper
{
private ISession session;
public RollbackHelper(ISession sessionToUse)
{
session = sessionToUse;
}
public void Execute(Action action)
{
using (ITransaction transaction = session.BeginTransaction())
{
action();
transaction.Rollback();
}
}
}
Then created a base class for all my test fixtures which test functionality against database. i named it BaseRepositoryFixture class. It contains an instance of RollbackHelper, ISessionFactory and ISession. It contains two methods used to set up and tear down needed data for current text fixture. They are decorated with TestFixtureSetUp and TestFixtureTearDown attributes. You can note that they are virtual methods. It is done in order to allow child classes to override them.
public class BaseRepositoryFixture
{
protected ISessionFactory sessionFactory;
protected ISession session;
protected RollbackHelper rollbackHelper;
[TestFixtureSetUp]
public virtual void TestFixtureSetUp()
{
Configuration configuration = new Configuration();
configuration.Configure();
configuration.AddAssembly(typeof(Module).Assembly);
sessionFactory = configuration.BuildSessionFactory();
session = sessionFactory.OpenSession();
rollbackHelper = new RollbackHelper(session);
}
[TestFixtureTearDown]
public virtual void TestFixtureTearDown()
{
session.Dispose();
sessionFactory.Dispose();
}
}
Here is a sample how to use these classes in unit tests.
[TestFixture]
public class ViewRepositoryFixture : BaseRepositoryFixture
{
Module module;
private View CreateTestView()
{
View view = new View()
{
Guid = Guid.NewGuid(),
Name = "Default",
RelativePath = @"\Default.aspx",
RowVersion = 1,
Module = module,
Description = "some description"
};
return view;
}
[TestFixtureSetUp]
public override void TestFixtureSetUp()
{
base.TestFixtureSetUp();
module = new Module()
{
Name = "Base module",
RowVersion = 1,
IsActivated = true,
Href = "http://basemodule",
Guid = Guid.NewGuid(),
DisplayName = "Base module",
Description = "Some Base module description"
};
session.Save(module);
session.Flush();
}
[TestFixtureTearDown]
public override void TestFixtureTearDown()
{
session.Delete(module);
session.Flush();
base.TestFixtureTearDown();
}
[Test]
public void AddNewView()
{
rollbackHelper.Execute(delegate()
{
View view = CreateTestView();
ViewRepository repository = new ViewRepository(session);
repository.Add(view);
View result = session
.CreateCriteria(typeof(View))
.Add(Expression.Eq("Id", view.Id))
.UniqueResult<View>();
Assert.IsNotNull(result);
});
}
}
In this test fixture I override both set up and tear down methods in order to create a module and to delete it after the tests. Note that in TestFixtureSetUp we call base class method before child implementation in order to use its protected variables. Note that in TestFixtureTearDown we call base class after deleting the module. In the test method AddNewView we create a delegate and provide it to Execute method of the RollbackHelper.
I have in my ViewRepository a method which retrieves a specific View but note that I don’t use it. Instead of using it I create a ICriteria and execute it in order to check if a view was added into database. I do that in order to be sure that all is correct. It is clear that if I use ViewRepository method for retrieving added view I can run into an error because of the ViewRepository method.