Unit testing an Activerecord domain model with NUnit

[Digression] This post has been sitting in a somewhat unfinished state for a long time. It feels good to finally post it!

In rewriting my baseball game, CSFBL, I made the decision to use Castle ActiveRecord (built on top of NHibernate) to handle the persistence of my domain model. As a result, I needed to find a simple but effective way to unit test this implementation.

The unit tests needed to accomplish the following:

  1. Test the initialization of ActiveRecord (which validates the database schema against the object mappings).
  2. Test object validation (since I am using Castle’s Validation component and the ActiveRecordValidationBase<T> base class).
  3. Ensure that a domain object can be persisted to the database.
  4. Ensure that a domain object can be retrieved from the database.
  5. Ensure that multiple instances of an object with the same persisted primary key are equal.

How, then, do we do this?

Testing against a real database with test data

One thing I knew I needed to do was to test against a true database instance, not a mock. I wanted to make sure this was a live test of the persistence model, not just a test of the conceptual model. Since CSFBL will only run off an MSSQL database, and the schema is constantly changing, and the domain model has many foreign key dependencies, unit testing against the database is important.

To accomplish this, I wrote a short SQL script which would insert test data into a blank copy of the database. (All database scripts and build scripts are also in SVN; I’ll blog about that solution another time.) By inserting well-defined test data, I can then write unit tests easily.

A sample of the SQL test script follows.

-- start bb_leagues
print 'bb_leagues'

declare @leagueid int

insert into bb_leagues ( name, createdon, simfrequency,
startyear, date, forumid, ispremier, stadiumchangeallowed )
values ( 'testleague', getdate(), 2, 1981, '1981-01-01', null, 0, 1 )

set @leagueid = scope_identity()
-- end bb_leagues

-- start bb_conferences
print 'bb_conferences'

declare @conferenceid int

insert into bb_conferences ( name, allowdh, leagueid )
values ( 'testconference', 0, @leagueid )

set @conferenceid = scope_identity()
--end bb_conferences

Notice how I saved the primary key of the first insert (a league) and used it on the second insert (a conference). By cascading down the dependency chain of my database model, I can easily build my test data.

The ActiveRecordTest base class

Once the test data is available, it’s a matter of writing the unit tests. Since there will be one test class for each model class (e.g. LeagueTests for the League class, which maps to bb_leagues), we can have an abstract base class for our unit tests that will provide baseline functionality.

The base class will be responsible for a number of tasks, each corresponding to an NUnit task.

  1. [TestFixtureSetUp] Initialize the ActiveRecord framework when the test fixture starts.
  2. [SetUp] Prepare the schema before each test (to ensure each test is somewhat atomic).
  3. [TearDown] Clean up after each test is run.
  4. [TestFixtureTearDown] Reset ActiveRecord after each text fixture ends to ensure other tests can don’t break when running step #1.

By abstracting lots of the functionality, you only need to override a few methods:

  • FetchARAssembly() gets the assembly which contains your ActiveRecord model classes. This should be overridden to return the assembly name containing your model classes.
  • FetchARConfig() gets the configuration source for ActiveRecord. The default implementation uses the ActiveRecord section in your application configuration file, which often is correct.
  • The other methods – TestFixtureSetUp, SetUp, TearDown, TestFixtureTearDown – only need to be overridden if you need additional functionality at those phases of your unit test. In all cases, you should always call the base class implementation first.

On to the code!

using System;
using System.Reflection;
using Castle.ActiveRecord;
using Castle.ActiveRecord.Framework;
using Castle.ActiveRecord.Framework.Config;
using NUnit.Framework;

namespace ComputerSims.Common.Tests
{
///
/// Abstract class for unit tests that depend on ActiveRecord.
/// Ensures that ActiveRecord is initialized before the tests are run.
///
public abstract class ActiveRecordTest
{
#region SetUp/TearDown
///
/// Sets up the test fixture.
///
///
/// When overriding, be sure to call the base method.
///
[TestFixtureSetUp]
protected virtual void TestFixtureSetUp()
{
InitFramework();
}

///
/// Sets up before each test.
///
///
/// When overriding, be sure to call the base method.
///
[SetUp]
protected virtual void SetUp()
{
ClearSchema();
}

///
/// Tears down after each test.
///
///
/// When overriding, be sure to call the base method.
///
[TearDown]
protected virtual void TearDown()
{
}

///
/// Tears down the test fixture.
///
///
/// When overriding, be sure to call the base method.
///
[TestFixtureTearDown]
protected virtual void TestFixtureTearDown()
{
ActiveRecordStarter.ResetInitializationFlag();
}
#endregion

#region Protected Methods
///
/// Clears (deletes) all data in the data schema to allow each
/// test to run in isolation.
///
///
/// Currently this does nothing, as schema creation is handled by SQL scripts.
/// However, to run tests in isolation, we should recreate the database using
/// default data each time a unit test is run. Implementing that functionality
/// here would support this.
///
protected virtual void ClearSchema()
{
}

///
/// Gets the ActiveRecord configuration.
///
/// The ActiveRecord configuration settings.
///
/// Default implementation uses
/// .
///
protected virtual IConfigurationSource FetchARConfig()
{
IConfigurationSource source = ActiveRecordSectionHandler.Instance;
return source;
}

///
/// Gets the assembly that contains ActiveRecord entities.
///
/// The assembly.
///
/// Default implementation returns the executing assembly.
///
protected virtual Assembly FetchARAssembly()
{
return Assembly.GetExecutingAssembly();
}
#endregion

#region Private Methods
private void InitFramework()
{
ActiveRecordStarter.Initialize(
this.FetchARAssembly(),
this.FetchARConfig());
}
#endregion
}
}

Note that there’s nothing specific to my domain model in there. This base class can therefore be used for any ActiveRecord-based domain model.

Writing an implementation of this class is easy — and I’ll write about that soon.

Leave a Reply

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

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>