Introduction
When developing modern business applications, a common architecture includes multiple layers. At the bottom of the stack is the database layer, either communicating directly with the database or alternatively using an Object Relational Mapping (ORM) framework such as Nhibernate or Entity Framework.
The one thing that we don't want to do is to allow parts of our application further up the chain to directly access database objects.
Why? Simply put, because there may be properties or methods within the database objects that should not be accessible outside of the database layer.
Commonly, we use Data Transfer Objects (DTOs) mapped to the original database objects with only the properties that you want to expose. However, this brings with it an additional process whereby each database object needs to be mapped to the relevant DTO(s) and vice versa.
Following the principles of Test Driven Development, we need to test these mappings.
Mapping a Database Object to its DTO
Let's look at the following database object:
public class Customer
{
public int Id { get; set; }
public string Forename { get; set; }
public string Surname { get; set; }
public DateTime DateOfBirth { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Address3 { get; set; }
public string Address4 { get; set; }
public string Postcode { get; set; }
}
And here is the DTO that it's going to be mapped to:
public class CustomerDto
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Address3 { get; set; }
public string Address4 { get; set; }
public string Postcode { get; set; }
}
We're going to use AutoMapper
to create the mapping between the two objects.
The Forename
and Surname
properties have different names in the DTO, so we need to explicitly map those. Everything else will be mapped automatically.
using AutoMapper;
using SampleApp.Dtos;
using SampleApp.Entities;
public static class AutoMapperConfiguration
{
public static void ConfigureMappings()
{
Mapper.CreateMap<Customer, CustomerDto>()
.ForMember(dest => dest.FirstName, opts => opts.MapFrom(source => source.Forename))
.ForMember(dest => dest.LastName, opts => opts.MapFrom(source => source.Surname));
}
}
Performing Unit Tests
So in this case, the Customer
class will be mapped to CustomerDto
. When we write our unit tests, we need to check that every property has been mapped correctly.
using AutoMapper;
using NUnit.Framework;
using Ploeh.AutoFixture;
[TestFixture]
public class AutoMapperConfigurationTests
{
private Fixture _fixture;
[TestFixtureSetUp]
public void TestFixtureSetUp()
{
_fixture = new Fixture();
AutoMapperConfiguration.ConfigureMappings();
}
[Test]
public void UseAssertToVerifyThatMappingsAreConfiguredBetweenCustomerAndCustomerDtoObjects()
{
var source = _fixture.Create<Customer>();
var result = Mapper.Map<CustomerDto>(source);
Assert.AreEqual(source.Forename, result.FirstName);
Assert.AreEqual(source.Surname, result.LastName);
Assert.AreEqual(source.Address1, result.Address1);
Assert.AreEqual(source.Address2, result.Address2);
Assert.AreEqual(source.Address3, result.Address3);
Assert.AreEqual(source.Address4, result.Address4);
Assert.AreEqual(source.Postcode, result.Postcode);
Assert.AreEqual(source.DateOfBirth, result.DateOfBirth);
}
}
Long winded, right? And what if we add another property to the CustomerDto
object and have forgotten (yes, it's bad practice) to update the test?
All tests will still pass and everyone is blissfully unaware that the new property isn't mapped. This is where ClassAssert
can help.
using System;
using System.Linq;
using NUnit.Framework;
public static class ClassAssert
{
public static void PropertiesAreEqual(object source, object target, string[] propertiesToIgnore = null)
{
foreach (var targetProperty in target.GetType().GetProperties())
{
if (propertiesToIgnore == null || !propertiesToIgnore.Contains(targetProperty.Name))
{
var sourceProperty = source.GetType().GetProperty(targetProperty.Name);
if (sourceProperty != null)
{
Assert.AreEqual(targetProperty.GetValue(target), sourceProperty.GetValue(source));
}
else
{
throw new MemberAccessException(string.Format
("Property '{0}' not found on source object.", targetProperty.Name));
}
}
}
}
}
Using ClassAssert
Using ClassAssert
allows us to rewrite our unit test like this:
using AutoMapper;
using NUnit.Framework;
using Ploeh.AutoFixture;
[TestFixture]
public class AutoMapperConfigurationTests
{
private Fixture _fixture;
[TestFixtureSetUp]
public void TestFixtureSetUp()
{
_fixture = new Fixture();
AutoMapperConfiguration.ConfigureMappings();
}
[Test]
public void UseClassAssertToVerifyThatMappingsAreConfiguredBetweenCustomerAndCustomerDtoObjects()
{
var ignoredProperties = new[] { "FirstName", "LastName" };
var source = _fixture.Create<Customer>();
var result = Mapper.Map<CustomerDto>(source);
ClassAssert.PropertiesAreEqual(source, result, ignoredProperties);
Assert.AreEqual(source.Forename, result.FirstName);
Assert.AreEqual(source.Surname, result.LastName);
}
}
Fewer lines, cleaner code and if any object is changed, the unit test includes their properties automatically.
As shown in the example, there are times when property names don't match up. We can cater to this by passing in the optional array of property names to ignore. You can then test those mappings in the usual way.
History
Originally published by Brent Jenkins at http://www.anterec.co.uk/articles/using-classassert-to-check-property-mappings-between-different-object-types/.