Archive for the 'NHibernate' Category

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