Sunday, February 19, 2012

Simple Circles–NHibernate Unit Testing (Part 7c)

This post is part of a series on building a simple to use web-based contact and customer relationship management application. The goal is to support various audiences including businesses, teams, clubs, religious organizations, etc.

In Part 7a I implemented the Repository pattern using NHibernate and in Part 7b I used Fluent NHibernate to implement the Data Mapper pattern between the domain layer and the relational database layer. In this post I’ll build unit tests to exercise these implementations.

One of the problems with attempting to test a data access strategy is ensuring the database is in a known state at the start of each test. Previously I’ve used teardown and setup SQL scripts to empty out data and preload initial values in a database instance. That approach is tedious because it means maintaining two sets of scripts in a different syntax (SQL instead of C#) and using a different toolset (SSMS). Another approach that’s becoming more popular and prevalent is to use a lighter footprint database engine capable of running in-memory. In fact, Fluent NHibernate makes this easy with its standard configuration – all it takes is setting up an NHibernate session configured to use the in-memory database as shown here:

SessionFactory Class
namespace Circles.NHibernateTests
{
  using Circles.NHibernateRepository;

  using FluentNHibernate.Cfg;
  using FluentNHibernate.Cfg.Db;

  using NHibernate;
  using NHibernate.Cfg;
  using NHibernate.Tool.hbm2ddl;

  public class SessionFactory
  {
    private static Configuration staticConfig;

    public static ISessionFactory CreateSessionFactory()
    {
      return
        Fluently.Configure()
          .Database(SQLiteConfiguration.Standard.InMemory().ShowSql())
          .Mappings(m => m.FluentMappings.AddFromAssemblyOf<PartyRepository>().ExportTo(System.Console.Out))
          .ExposeConfiguration((c) => staticConfig = c)
          .BuildSessionFactory();
    }

    public static void BuildSchema(ISession session)
    {
      var export = new SchemaExport(staticConfig);
      export.Execute(script: true, export: true, justDrop: false, connection: session.Connection, exportOutput: null);
    }
  }
}
Lines 20-22 contain all the “magic”: line 20 configures the database to use the standard in-memory configuration for SQLite. Line 21 sets up the mappings to be read from the PartyRepository assembly; in addition, the ExportTo() method call will dump the generated hbm.xml to the console. Line 22 exposes the configuration being set up before it is passed to NHibernate for session creation. In this case, the Lambda expression stores the newly built configuration in a static variable for later reuse by the BuildSchema method.

xUnit does not implement or use constructs such as [Setup] / [Teardown] like nUnit does or [TestInitialize] / [TestCleanup] like MSTest does since the authors feel it is problematic. You can read more about the rational on James Newkirk’s blog post “Why you should not use Setup and TearDown in NUnit”. Pretty compelling when the main author himself tells you not to; however, I’m going to bend the rules a little. I’ve implemented a base testing class that contains a virtual method called TestInitialize() whose purpose is to prepare the testing database and put it in a known state before each test is executed:

TestBase Class
namespace Circles.NHibernateTests
{
  using NHibernate;

  /// <summary>
  /// Base class for NHibernate / SQLite tests
  /// </summary>
  public abstract class TestBase
  {
    protected static readonly object LockObject = new object();

    private static ISessionFactory sessionFactory;

    protected ISession Session { get; set; }

    /// <summary>
    /// Code to run before the test to allocate 
    /// and configure any resources needed.
    /// </summary>
    public virtual void TestInitialize()
    {
      sessionFactory = SessionFactory.CreateSessionFactory();
      Session = sessionFactory.OpenSession();
      SessionFactory.BuildSchema(Session);
    }

    public void Dispose()
    {
      Session.Dispose();
    }
  }
}

Each test class can now easily prepare an in-memory database and place it in a known state prior to executing tests by overriding TestInitialize() with its own specific behavior:

TestInitialize() Method
public override void TestInitialize()
{
  Monitor.Enter(LockObject);

  base.TestInitialize();

  using (var tx = Session.BeginTransaction())
  {
    var householdType = new PartyType(new Guid(Constants.PartyTypeIdHousehold))
    {
      TypeName = "Household"
    };
    Session.Save(householdType);

    var personType = new PartyType(new Guid(Constants.PartyTypeIdPerson))
    {
      TypeName = "Person"
    };
    Session.Save(personType);

    var person = new Person
    {
      FirstName = "Nancy",
      LastName = "Davolio",
      Gender = GenderEnum.Female,
      Birthday = new DateTime(1948, 12, 8),
      PartyType = personType
    };
    Session.Save(person);

    Session.Flush();
    tx.Commit();
  }

  Session.Clear();

  Monitor.Exit(LockObject);
}

The Monitor.Enter and Monitor.Exit calls (lines 3 & 38) ensure that access to the NHibernate Session remains serialized and two test methods can’t accidently interfere with each other in multi-threaded harnesses. The first call is to the base class’ implementation which creates the in-memory database. Following that I create PartyType instances for Household and Person and then create and persist a well-known Person instance.

With the base class and SessionFactory implemented and in place, constructing unit tests that can exercise (indirectly) the mappings as well as an actual, deployed database layer becomes trivial:

CanAddHousehold() Test Method
[Fact]
public void CanAddHousehold()
{
  this.TestInitialize();

  IPartyRepository repository;
  Household household;

  // arrange
  using (var tx = Session.BeginTransaction())
  {
    repository = new PartyRepository(Session);
    var partyType = repository.FindPartyTypeById(new Guid(Constants.PartyTypeIdHousehold));

    // act
    household = new Household
    {
      PartyName = "Eisenhower",
      FormalGreeting = "President Dwight D. and Mrs. Mamie",
      InformalGreeting = "Ike and Mamie",
      PartyType = partyType
    };

    repository.Add(household);
    tx.Commit();
  }

  // assert
  Assert.NotNull(household);
  Assert.NotNull(repository.FindById(household.PartyId));
}

Line 4 initializes the database including preparing the known state, Line 12 initializes a PartyRepository instance using the in-memory database session. This particular line of code deserves special attention – I’m creating a PartyRepository and giving it the in-memory database to operate against. Recall in Part 7a on Line 47 of the fourth code fragment (DBSession class)  that a PartyRepository instance was created using the “real” NHibernate DbSession there. Here’s where designing the NHibernate repository to accept an ISession implementation gives flexibility.

Line 13 retrieves the household party type that is known to be there because it was created and saved during TestInitialize(). Finally the household is created and saved. An interesting side effect of this test was when I first ran it – it failed. I had forgotten to specify/assign a PartyType to the household instance. When I referred back to the earlier tests that I put together using the FakeRepository I realized that it too wasn’t assigning or checking the party type. This oversight illustrates a problem with trying to fake or mock an implementation – you have to remember to code and test all the validation and business rules necessary to be a “real” repository. This is the best reason to have taken these steps of setting up a reusable, fast way to test the real repository – as you code the repository, you can test it without having to write double the code by updating the fake one.

This discovery led to adding a negative test to ensure the repository doesn’t allow a household without a party type:

Negative Test
[Fact]
public void CanNotAddHouseholdWithMissingPartyType()
{
  this.TestInitialize();

  // arrange
  using (var tx = Session.BeginTransaction())
  {
    var repository = new PartyRepository(Session);

    // act
    var household = new Household
    {
      PartyName = "Eisenhower",
      FormalGreeting = "President Dwight D. and Mrs. Mamie",
      InformalGreeting = "Ike and Mamie",
      /* PartyType = partyType */
    };

    // assert
    Assert.Throws<NHibernate.PropertyValueException>(
      delegate
      {
        repository.Add(household);
      });

    tx.Rollback();
  }
}

Lines 21-25 uses the xUnit Assert.Throws<> method to wrap the attempt to add a household without a party type. If the repository doesn’t throw an NHibernate.PropertyValueException then the test is considered to have failed.

This post was pretty densely packed with concepts - an entire framework for testing an NHibernate-based data layer. The accompanying code base is growing larger and can be downloaded from here.

No comments:

Post a Comment