Testing NHibernate Repositories

The Repository pattern is a technique used to manage the persistence of objects to a relational database, while decoupling the domain objects from the persistence technology. An Object-Relational Mapper (ORM), such as NHibernate can be used to map objects to database tables.

There is not usually much logic to test in a repository class, except for the interaction with the ORM, so there is often little need for testing the repository class independently from the database. What we really care about is the integration with the underlying data store. We can write integration tests to ensure this interaction is working correctly.

When executing tests against a database, tables can become cluttered with test data, often causing conflicts with subsequent tests. One solution is to run a SQL script before the tests are run which resets the database. However, this means we have to maintain a separate database script. If the database schema changes, we have to make sure this script is updated, or the tests might fail.

The open-source CodeCampServer project offers a great solution to testing NHibernate repository classes against a database. This involves recreating the test database schema from the NHibernate mapping files before each test is run.

Creating the database schema

The NHibernate SchemaExport method can be used to generate database schema from the .hbm mapping files. This allows us to create the test database schema without the need to maintain a separate database script.

   1: var exporter = new SchemaExport(new HybridSessionBuilder().GetConfiguration());
   2: exporter.Execute(false, true, false, true);

By placing this code in the test fixture SetUp method, the database schema is recreated before each test is run, ensuring there are no conflicts with data from previous tests.

A base class can be created to provide this functionality for any test fixture that derives from it.

   1: public class DatabaseTesterBase : RepositoryBase
   2: {
   3:     public DatabaseTesterBase() : base(new HybridSessionBuilder())
   4:     {
   5:     }
   6:  
   7:     [SetUp]
   8:     public virtual void Setup()
   9:     {
  10:         recreateDatabase();
  11:     }
  12:  
  13:     public static void recreateDatabase()
  14:     {
  15:         var exporter = new SchemaExport(new HybridSessionBuilder().GetConfiguration());
  16:         exporter.Execute(false, true, false, true);
  17:     }
  18: }

To create the database schema, the data type must be specified for each primary key column mapping. NHibernate derives the SQL types for the other columns from the .NET type associated with each mapping. The generated schema may not exactly match the real database schema, but it is sufficient enough to test the object persistence.

   1: <class name="Person" table="People">
   2:     <id name="Id" column="Id" type="Guid">
   3:         <generator class="guid.comb"/>
   4:     </id>
   5:  
   6:     <component name="Contact" class="Contact">
   7:         <property name="FirstName" length="20" not-null="true"/>
   8:         <property name="LastName" length="20" not-null="true"/>
   9:         <property name="Email" length="60" not-null="true"/>
  10:     </component>
  11:     <property name="Website" not-null="false"/>
  12:     <property name="Comment" not-null="false"/>
  13:     <property name="Password" length="256"/>
  14:     <property name="PasswordSalt" length="256"/>
  15:     <property name="IsAdministrator" not-null="true" />
  16:  
  17: </class>

It is preferable to run the tests against a local database, as this will help increase test performance and prevent conflicts with other developers running tests against a shared database.

Writing the tests

Here I am using an example of a repository integration test from the CodeCampServer project.

The PresonRespositoryTester class derives from DatabaseTesterBase, which provides the schema export functionality.

   1: [TestFixture]
   2: public class PersonRepositoryTester : DatabaseTesterBase
   3: {
   4:     ...
   5: }

Any required test data is created using the NHibernate session directly. The test data objects are saved to the session and flushed to the database.

   1: [Test]
   2: public void ShouldSavePersonToDatabase()
   3: {
   4:     Conference theConference = new Conference("foo", "");
   5:     using (ISession session = getSession())
   6:     {
   7:         session.SaveOrUpdate(theConference);
   8:         session.Flush();
   9:     }
  10:     ...
  11: }

Next, the SUT (System Under Test) is exercised by calling the Save method on the repository.

   1: [Test]
   2: public void ShouldSavePersonToDatabase()
   3: {
   4:     Conference theConference = new Conference("foo", "");
   5:     using (ISession session = getSession())
   6:     {
   7:         session.SaveOrUpdate(theConference);
   8:         session.Flush();
   9:     }
  10:     Person person = new Person("Andrew","Browne", "");
  11:     person.Conference = theConference;
  12:     person.Website = "";
  13:     person.Comment = "";
  14:  
  15:     IPersonRepository repository = new PersonRepository(_sessionBuilder);
  16:     repository.Save(person);
  17:     ...
  18: }

Finally, the results are loaded back from the database and verified against a set of expectations.

   1: [Test]
   2: public void ShouldSavePersonToDatabase()
   3: {
   4:     Conference theConference = new Conference("foo", "");
   5:     using (ISession session = getSession())
   6:     {
   7:         session.SaveOrUpdate(theConference);
   8:         session.Flush();
   9:     }
  10:     Person person = new Person("Andrew","Browne", "");
  11:     person.Conference = theConference;
  12:     person.Website = "";
  13:     person.Comment = "";
  14:  
  15:     IPersonRepository repository = new PersonRepository(_sessionBuilder);
  16:     repository.Save(person);
  17:  
  18:     Person rehydratedPerson = null;
  19:     //get Person back from database to ensure it was saved correctly
  20:     using (ISession session = getSession())
  21:     {
  22:         rehydratedPerson = session.Load<Person>(person.Id);
  23:  
  24:         Assert.That(rehydratedPerson != null);
  25:         Assert.That(rehydratedPerson.Contact.FirstName, Is.EqualTo("Andrew"));
  26:         Assert.That(rehydratedPerson.Contact.LastName, Is.EqualTo("Browne"));
  27:     }
  28: }

These tests will run much slower than standard unit tests, but that’s fine, as integration tests should be run less often than unit tests.

By generating the database schema before each test is run, we can ensure each test executes against a known set of data and is not affected by data generated from other tests. This helps us to create reliable and consistent data access integration tests.

Advertisements

7 Responses to “Testing NHibernate Repositories”


  1. 1 Tobin Harris October 7, 2008 at 10:22 am

    This is pretty much how I test my repositories. I use the in-memory db SQLite, and there is about a 5 second warm-up time, then tests run incredibly quickly.

    I still find problems finding the energy to keep these kinds of tests separate from other tests (that include business logic). It seems like I want to test that my persistence is working as expected, in the context of business logic.

    Oh, you may want to take a look at the Fluent NHibernate stuff. They have some helper methods for testing that mappings work with very little code.

  2. 2 timross October 7, 2008 at 11:47 am

    Thanks for your comments, Tobin. I will definitely look at using SQLite for the data access integration tests. Can’t wait for your NHibernate in Action book to come out!

  3. 3 Eldon Ferran de Pol October 7, 2008 at 1:04 pm

    Tim,

    Good stuff and very interesting. I can see the benefit of using nHibernate to generate your database at an early stage of development but once you have database artifacts outside the reach of nHibernate (indexes, views or, heaven forbid, stored procedures) do you not end up needing scripts to manage these anyway? Particularly once you go past your first live release and either need incremental database scripts for each future deployment or have to find differences between your development db and prod db.

    One of the key benifits I have found with maintaining incremental database scripts is that these prove useful for more than just providing a stable base to run unit tests against. They should fit with your deployment model and allow you to view each set of schema and/or data changes as a versionable change that ties in with a corresponding set of code changes. This allows you to roll both code and database schema verions backwards and forwards at will.

    It then becomes pretty straightforward to have a build process that runs all previous scripts prior to running the unit tests. This has the added benefit of testing that your scripts will indeed work against your current code base once you come to deploy.

    Does using nHibernate to generate your database not leave you need

  4. 4 Dermot October 7, 2008 at 1:07 pm

    Good article Tim, very helpful. I think this calls for a whole series on integration tests 😉

  5. 5 timross October 7, 2008 at 4:03 pm

    Thanks for your reply, Eldon. Incremental scripts are indeed useful for versioning the database schema. However, the database generated to test your repositories need not represent the full database schema. Our purpose here is to test the interaction between the repository classes and the persistence technology. The generation of a database is merely a side-effect of this. And as mentioned by Tobin above, an in-memory database can serve this purpose quite nicely.

    If a repository calls a stored procedure, or requires a specialised database feature, then it will need to call a “real” database rather than one generated from the mapping files.

    Also, if you already have a process in place to manage migration scripts, then it is probably worth continuing to run your tests against those because, as you mentioned, your integration tests will also ensure your scripts generate schema that works with your code.

  6. 6 Tobin Harris February 3, 2009 at 9:30 pm

    @Eldon

    NHibernate mapping files will let you create other database artifacts too or run arbitrary SQL. You can even specify SQL on a per-platform basis. All that is covered in NHibernate in Action, but if you don’t have try Googling for “database-object” or “IAuxiliaryDatabaseObject”.

    I like your approach of using incremental scripts, having worked with the migrations library in Ruby on Rails.

    The vote is out as to whether I prefer the “Generate database using NHibernate then use SqlDelta to generate dev->live migrations” or if I prefer the “Use migration library/scripts to layer up incremental changes”.

    Like everything in our industry, there’s pros and cons to both I guess 🙂

  7. 7 PXStevey January 25, 2011 at 7:59 am

    Hi, I’m new I would like to welcome all… 🙂


Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s





%d bloggers like this: