EDIT: To access the codebase below, the user name is “guest” and the password is blank. http://codebetter.com/blogs/jeremy.miller/archive/2008/02/01/access-to-the-storyteller-source-code.aspx
Yesterday,
David Laribee related some problems he experienced with refactorings in his
domain model leading to some breaking problems with NHibernate mappings.
Specifically, the issues are:
- Changing the property names of a domain model can break the NHibernate mapping
- Changing the database fields can break the NHibernate mappings
David went on to bemoan the absence of a quick way to validate NHibernate
mappings. Ayende popped in with the suggestion that the presence of
integrated tests around the NHibernate usage would spot mapping problems.
Other folks mentioned that there’s a new ReSharper plugin to validate and
refactor NHibernate mappings. I’ll circle back to the refactoring plugin
in a while.
First I want to talk about quick ways to validate NHibernate
mappings. Ayende is right of course about the integrated tests against the
individual queries, but I’m going to suggest that that isn’t the most efficient
answer to the question of validating the mappings. The bigger integration tests will tell you that something is
wrong, but from experience they’ll be harder to diagnose because there is so
much more going on than simple property checking, and they provide a slow
feedback cycle because of how much stuff is going on. What would be nice
is a way to walk right up to a mapping and specify which properties on a class
are supposed to be persisted and how.
I thought I would come out of my blogging retirement and show an example of the
Chad and Jeremy
approach to testing NHibernate mappings:
[SetUp] public void SetUp() { // Ensure that the StructureMap configuration is bootstrapped // In our case, this includes everything we need setup to // execute NHibernate (mappings + ISessionFactory configuration) // This will be pretty application specific here Bootstrapper.RestartStructureMap(); } [Test] public void SaveAndLoadCustomerContact() { new PersistenceSpecification<CustomerContact>() .CheckProperty(x => x.Name, "Frank") .CheckProperty(x => x.Email, "Email") .CheckProperty(x => x.Extension, 123) .CheckProperty(x => x.FaxNumber, "111-111-1111") .CheckProperty(x => x.TelephoneNumber, "222-222-2222") .CheckProperty(x => x.Title, "Mr.") .VerifyTheMappings(); }
All this test does is ensure that the 6 properties of the CustomerContact class
(Name, Email, Extension, FaxNumber, TelephoneNumber, Title) are mapped in
NHibernate. We have some other methods for checking to many and many to
one type relationships.
Behind the scenes the PersistenceSpecification<T> class:
- Creates a new instance of T
- Uses the lambda expressions and suggested values in the calls to CheckProperty
to load values into the new instance of T - Grabs our IRepository out of StructureMap (of course), and saves the object
- Grabs a second IRepository out of StructureMap
- Fetches a second copy of the same T out of the second IRepository
- Verifies that all of the specified properties in the specification were saved
and loaded. If any single property does not match between the first T and
the second T, the test will fail.
Here’s the implementation of the PersistenceSpecification.VerifyTheMappings() method:
public void VerifyTheMappings() { // Create the initial copy var first = new T(); // Set the "suggested" properties, including references // to other entities and possibly collections _allProperties.ForEach(p => p.SetValue(first)); // Save the first copy _repository.Save(first); // Get a completely different IRepository var secondRepository = createRepository(); // "Find" the same entity from the second IRepository var second = secondRepository.Find<T>(first.Id); // Validate that each specified property and value // made the round trip // It's a bit naive right now because it fails on the first failure _allProperties.ForEach(p => p.CheckValue(second)); }
The advantage of this testing is that it gives a (relatively) fast feedback cycle
focused specifically on the mappings. Tools that check the hbm.xml
mappings can only verify that what’s there is correctly formed. The
mapping tests above will catch missing mappings and desired behavior. At a
bare minimum, you really should have at least one smoke test in your CI build
that simply tries to create an NHibernate ISession object from your
configuration. Let that test run and possibly fail the build before wasting any time on integrated
tests that can’t succeed.
Now, the ReSharper plugin for NHibernate sounds pretty cool. I definitely
want little or no friction in renaming or adding properties in my Domain Model
classes (why I absolutely despise codegen your business object solutions).
We beat the refactoring problem by eliminating HBM.XML. As part of my New
Year’s resolution to eliminate my exposure to angle bracket hell, we’ve created
the beginning of a Fluent Interface API to express NHibernate mappings.
Using copious amounts of Generics (I guess .Net code just “wants” to have lots
of angle brackets) and lambda expressions, we’re able to express NHibernate
mappings in a completely compiler-checked, ReSharper-able way. Since we
switched to the FI, we’ve experienced far less trouble with mapping problems.
Here’s a couple of examples:
// Simple class with properties and a single "to-many" relationship public class CustomerContactMap : ClassMap<CustomerContact> { public CustomerContactMap() { Map(x => x.Name); Map(x => x.Email); Map(x => x.Extension); Map(x => x.FaxNumber); Map(x => x.TelephoneNumber); Map(x => x.Title); References(x => x.Customer); } } // Class with a "Component" public class CustomerDeliveryAddressMap : ClassMap<CustomerDeliveryAddress> { public CustomerDeliveryAddressMap() { Map(x => x.Name); References(x => x.Customer); Component<Address>(x => x.Address, m => { m.Map(x => x.AddressLine1); m.Map(x => x.AddressLine2); m.Map(x => x.AddressLine3); m.Map(x => x.CityName); m.Map(x => x.CountryName); m.References(x => x.State); m.References(x => x.PostalCode); }); } } // Class with some "has many" relationships public class CustomerMap : ClassMap<Customer> { public CustomerMap() { HasMany<CustomerContact>(x => x.Contacts).CascadeAll(); HasMany<CustomerJob>(x => x.Jobs).CascadeAll(); HasMany<CustomerDeliveryAddress>(x => x.DeliveryAddresses).CascadeAll(); Map(x => x.Name); Map(x => x.LookupName); Map(x => x.IsGeneric); Map(x => x.RequiresPurchaseOrder); Map(x => x.Retired); } }
With this approach, and backed up with the little PersistenceSpecification tests,
we can happily change class names and property names with relative confidence.
Besides, the simple usage of Intellisense plus compiler safe code cuts down on
the number of mapping errors. We’re more or less greenfield at the moment, so we can get away with generating
the database from our NHibernate mappings on demand, but you can specify
specific table and column names in the language above for brownfield scenarios.
I’d very confidently say that we’re faster with this approach than we would be
with HBM.XML.
If you’re interested, the complete code for everything I talked about is in the ShadeTree.DomainModel project in the StoryTeller codebase (and effectively released under the Apache 2.0 license). The code is at
http://storyteller.tigris.org/svn/storyteller/trunk/. Use the src\ShadeTree.sln for this stuff. I don’t know that we’ll ever get around to a fully supported release of this stuff, but I wanted to throw out the idea anyway. At this point I’m only extending this code when we need something new for our current project.
A lot of the advantages of this approach are tied to application specific
conventions and also by tieing the forward generation of the database structure
to validation rules.
As for IoC container testing…
I’ll overlook the fact that my friend David also implicitly implied that an
IoC container not named StructureMap was the de facto standard.
Bil Simser posted a little snippet of code to smoke test the configuration of one of those other IoC containers. StructureMap has had quite a bit
of diagnostic support since version 0.85 (StructureMapDoctor.exe), but StructureMap 2.5 will add the
ability to do this:
[SetUp] public void SmokeTestStructureMapConfiguration() { ObjectFactory.AssertConfigurationIsValid(); }
This will attempt to build every possible configured instance in StructureMap, perform any designated
environment tests (like trying to connect to a database), and generate a complete report of all errors encountered by StructureMap.
If you’re aggressive about managing all external services and configuration into your IoC container, this diagnostic
test can go a long way towards detecting environmental and configuration errors of a code installation.