In a domain model, you will sometimes encounter immutable data – objects that can only be read from a database, but never modified. The question is how to test your persistence layer to ensure that you can read them. One way is to hard code values into your tests and assume that the appropriate rows are in the database. This is rather fragile as you are counting on your database being in a known good state. This also prevents you (or another developer) from getting latest from source control onto a clean developer workstation and being able to run the unit tests immediately. You must populate a test database with known good data first. Here’s a little trick you can play with NHibernate. Let’s say you have the following class:
namespace JamesKovacs.Examples.ImmutableData { public class Invoice { private readonly int m_Id = 0; private readonly string m_InvoiceNumber; private readonly decimal m_Amount; private readonly DateTime m_InvoiceDate; public Invoice() { } public Invoice(string invoiceNumber, decimal amount, DateTime invoiceDate) { m_Amount = amount; m_InvoiceDate = invoiceDate; m_InvoiceNumber = invoiceNumber; } public int Id { get { return m_Id; } } public string InvoiceNumber { get { return m_InvoiceNumber; } } public DateTime InvoiceDate { get { return m_InvoiceDate; } } public decimal Amount { get { return m_Amount; } } } }
And its corresponding NHibernate mapping file:
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="JamesKovacs.Examples.ImmutableData" assembly="JamesKovacs.Examples.ImmutableData" default-access="field.pascalcase-m-underscore"> <class name="Invoice" mutable="false"> <id name="Id"> <generator class="native" /> </id> <property name="InvoiceNumber"/> <property name="Amount"/> <property name="InvoiceDate"/> </class> </hibernate-mapping>
Note that the Invoice is lookup data only. So it is declared as immutable in the mapping file (mutable=”false”). NHibernate will not permit INSERT, UPDATE, or DELETE operations against the entity. The question is how do we test this? We could circumvent NHibernate and directly insert test data into the table via ADO.NET. The downside is that you’re coupling your test data generation code to a particular database schema. If the database schema changes, you must not only update the NHibernate mapping, but you must also modify your SQL statements for creating test data. A definite violation of DRY (Don’t Repeat Yourself) Principle. Let’s see if there is a better way…
NHibernate can generate the database schema from mapping files using NHibernate.Tool.hbm2ddl.SchemaExport. The problem is that you can’t insert data into the Invoice table since the type is mapped as immutable. However, you can read the NHibernate configuration and mapping files and modify the mapping at runtime. This is done by cfg.GetClassMapping(typeof(Invoice)).IsMutable = true. We’re switching our immutable entity to a mutable one on the fly for the purposes of populating test data! Note that you have to modify the mapping before creating your SessionFactory.
using System; using JamesKovacs.Examples.ImmutableData; using MbUnit.Framework; using NHibernate; using NHibernate.Cfg; using NHibernate.Tool.hbm2ddl; namespace JamesKovacs.Examples.ImmutableData.Tests { [TestFixture] public class InvoiceRepositoryTests { private const string InvoiceNumber = "ABCD1234"; // N.B. You should do this once before all your repository tests // as creating a SessionFactory and executing DDL is time-consuming. [SetUp] public void Setup() { Configuration cfg = new Configuration().Configure(); SchemaExport exporter = new SchemaExport(cfg); exporter.Execute(false, true, false, false); cfg.GetClassMapping(typeof(Invoice)).IsMutable = true; ISessionFactory sessionFactory = cfg.BuildSessionFactory(); using(ISession session = sessionFactory.OpenSession()) { session.BeginTransaction(); Invoice invoice = new Invoice(InvoiceNumber, 42m, DateTime.Today); session.Save(invoice); session.Transaction.Commit(); } } [Test] public void CanRetrieveInvoice() { using(UnitOfWork.Start()) { IInvoiceRepository repo = new InvoiceRepository(); Invoice invoice = repo.FindByInvoiceNumber(InvoiceNumber); Assert.IsNotNull(invoice); Assert.AreEqual(InvoiceNumber, invoice.InvoiceNumber); } } } }
So we now have an elegant solution for populating immutable test data in a clean database without resorting to hand-coded SQL.
N.B. UnitOfWork.Start() sets up an NHibernate session that is used internally by the repositories. It is based on Oren Eini’s UnitOfWork implementation in Rhino Commons.