Saturday, April 03, 2010

Testing NHibernate mappings

I’m currently in the process of moving Suteki Shop from Linq-to-SQL to NHibernate. It turns out to be a bigger job than I anticipated, and I’ll be writing a post about my experiences soon. I’m using FNH (Fluent NHibernate) to build my mapping files rather than use the tedious built-in XML mapping. One of the big jobs I have is to write unit tests for all the mapping files. For each entity, I want to have a test that creates an instance of it, saves it to the database, and retrieves it. As you can imagine, it would be a tedious task to write all these. With that in mind I’ve created a simple NUnit test that can be reused for any mapped entity:

using System;
using System.Reflection;
using NUnit.Framework;
using Suteki.Common.Extensions;
using Suteki.Shop.Tests.Models.Builders;

namespace Suteki.Shop.Tests.Maps
{
    /// <summary>
    /// Runs a simple test for each entity listed. That it can be saved to the database and retrieved.
    /// </summary>
    [TestFixture]
    public class SimpleMapTests : MapTestBase
    {
        [TestCase(typeof (PostZone))]
        [TestCase(typeof (Postage))]
        [TestCase(typeof (Country))]
        public void SimpleMapTest(Type entityType)
        {
            var runSimpleMapTest = GetType().GetMethod("RunSimpleMapTest");
            var runSimpleMapTestForType = runSimpleMapTest.MakeGenericMethod(entityType);
            runSimpleMapTestForType.Invoke(this, new object[0]);
        }

        public void RunSimpleMapTest<TEntity>() where TEntity : class, new()
        {
            var id = 0;

            InSession(session =>
            {
                var entity = GetDefaultsFor<TEntity>();

                session.Save(entity);
                id = (int) entity.GetPrimaryKey().Value;
            });

            InSession(session => session.Get<TEntity>(id));
        }

        static TEntity GetDefaultsFor<TEntity>()
        {
            var entityName = typeof (TEntity).Name;
            var defaults = typeof(Default).GetMethod(entityName, BindingFlags.Static | BindingFlags.Public);
            if (defaults == null)
            {
                Assert.Fail(string.Format("No static method Default.{0} found", entityName));
            }
            if (defaults.ReturnType != typeof (TEntity))
            {
                Assert.Fail(string.Format("Method Default.{0} does not have a return type of {0}", entityName));
            }

            return (TEntity) defaults.Invoke(null, new object[0]);
        }
    }
}

The core idea here is to use the NUnit TestCase attribute to run a test for each entity that I want to map. I have a generic test method ‘RunSimpleMapTest<TEntity>’ that gets called for each entity by ‘SimpleMapTest’. There’s a little bit of reflection voodoo to create and run a correctly typed ‘RunSimpleMapTest’, but the principle is simple.

RunSimpleMapTest gets a default instance of the entity class, saves it, grabs the generated id, and then retrieves the entity again using the id. This is enough to check that the basic map works and that I don’t have any non-virtual members in my entity. The ‘InSession’ method is just to save some typing, it looks like this:

protected void InSession(Action<ISession> action)
{
    using (var session = InMemoryDatabaseManager.OpenSession())
    using(var transaction = session.BeginTransaction())
    {
        action(session);
        transaction.Commit();
    }
}

The GetPrimaryKey method is an extension method that finds the primary key of any entity based on a simple convention; my primary keys are always called ‘Id’ or ‘<entity name>Id’.

I’m using an in-memory SQLite database for the tests that’s managed by the MapTestBase base class. This is such a common solution that I’ll just leave you to Google it rather than showing my implementation here.

The last thing to note, is where the actual entity instance comes from. The GetDefaultsFor<TEntity> method looks for a method with the same name as the entity in a class called ‘Default’. The default method for PostZone looks like this:

public static class Default
{
    public static PostZone PostZone()
    {
        return new PostZone
        {
            Name = "UK",
            Multiplier = 1.0M,
            AskIfMaxWeight = false,
            Position = 1,
            IsActive = true,
            FlatRate = 10M
        };
    }

    .... lots of other similar methods
}

Now for each entity, I simply create the ClassMap, create a method to make a default entity in the Default class and add a new TestCase attribute to my test.

5 comments:

yellowfeather said...

Have a look at the PersistanceSpecification in FluentNhibernate http://wiki.fluentnhibernate.org/Persistence_specification_testing

Mike Hadlow said...

@yellowfeather, that's very cool. I might update my test cases to use it.

Thanks

Alexey Diyan said...

Hi, thanks for your post.

This is really interesting approach.

FluentNhibernate's Persistance Specification Testing gives you more control but also requires a little bit more code.

In general, I would recommend Persistance Specification Testing.

There are a lot of situations when your entity has not null FK, so you must create one or several "parent" entities related to entity-under-test in order to save it in database.

Mahesh Velaga said...

That InSession thingy is nice! :)

Anonymous said...

Why not use Entities?