When working with systems, mapping and serialization of data between objects happens frequently, for example:

  • Mapping database data via Entity Framework Entities to DTOs (and back) as data is transmitted across service boundaries.
  • Serializing DTOs to XML as they get transmitted as SOAP (WCF) messages.
  • Serializing DTOs to JSON as they are sent as RabbitMQ messages or transmitted to clients as data in a rest API.
  • Mapping DTOs to MVC View Models as we show data to users, or update data.
  • Taking data from View Models and creating HTML forms and receiving that data as a HTTP form post.

All of these represent opportunities for data to be lost, or for the data from one property to end up being copied by accident to another.  To make sure that all the expected properties were copied across, I used to write unit tests like this, which took a fair amount of time to write.

public void OldStyleTest()
{
  // Arrange.
  var dto = new ClientDto()
  {
    ClientId = 10,
     ClientName = "Test Client Name",
     IsDeleted = true,
     Contacts = new List<ContactDto>()
     {
       new ContactDto() { ContactId = 1, Name = "ContactDto1" },
       new ContactDto() { ContactId = 2, Name = "ContactDto2" },
     }
   };
 
   // Act.
   var model = new ClientModel(dto);
   
   // Assert.
   Assert.That(dto.ClientId, Is.EqualTo(model.ClientId));
   Assert.That(dto.ClientName, Is.EqualTo(model.ClientName));
   Assert.That(dto.IsDeleted, Is.EqualTo(model.IsDeleted));
   // Now do it for the rest of the properties...
}

But then I found DeepEqual and AutoFixture, which help you to cut out the boiler plate code:

public void ItIsPossibleToMapClientDtoToClientModelWithoutDataLoss()
{
   var fixture = new Fixture();

   var dto = fixture.Create<ClientDto>();
   var model = new ClientModel(dto);

   dto.ShouldDeepEqual(model);
}

AutoFixture creates unique test data in all of the properties of the object, including lists while DeepEqual provides a number of extension methods which allows you to test equality of values in an object graph matches.  Properties missing in one side of the relationship can be ignored using the WithDeepEqual extension method (https://github.com/jamesfoster/DeepEqual/blob/master/Readme.md)

This makes it much faster to write tests which test the mapping between code objects, but…

This week, I found a place where the serialization of objects was a problem. Who would have thought that adding an [Obsolete] attribute to a property would mean that the XML serializer would skip it?

void Main()
{
   var data = new Data() { A = "Property A", B = "Property B" };

   Console.WriteLine(XmlSerialize<Data>(data));
   Console.WriteLine();
   Console.WriteLine(DataContractSerialize<Data>(data));
   Console.WriteLine();
   Console.WriteLine(JavaScriptSerialize<Data>(data));
}

public string XmlSerialize<T>(T o)
{
   var xmlSerializer = new XmlSerializer(typeof(T));

   using(var ms = new MemoryStream())
   {
      xmlSerializer.Serialize(ms, o);
      return Encoding.UTF8.GetString(ms.ToArray());
   }
}

public string DataContractSerialize<T>(T o)
{
   var serializer = new DataContractSerializer(typeof(T));

   using(var ms = new MemoryStream())
   {
      serializer.WriteObject(ms, o);
      return Encoding.UTF8.GetString(ms.ToArray());
   }
}

public string JavaScriptSerialize<T>(T o)
{
   var serializer = new JavaScriptSerializer();
   return serializer.Serialize(o);
}

public class Data
{
   [Obsolete]
   public string A { get; set; }

   public string B { get; set; }
}

The output is as follows, note how the XML serializer has behaved differently, losing the value of property “A”.


<?xml version="1.0"?>
<Data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<B>Property B</B>
</Data>

<Data xmlns="http://schemas.datacontract.org/2004/07/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><A>Property A</A><B>Property B</B></Data>

{"A":"Property A","B":"Property B"}

Fortunately, using DeepEqual and AutoFixture, you can write a reusable test which carries out a roundtrip data-loss test in just a few lines of code:

private void ValidateSerialization(Type t)
{
   var fixture = new Fixture();

   var instance = Activator.CreateInstance(t);
   var dto = fixture.Create(instance);

   var xml = Serialize(t, dto);

   var deserializedDto = Deserialize(t, xml);

   try
   {
      dto.ShouldDeepEqual(deserializedDto);
   }
   catch (Exception ex)
   {
      Debug.WriteLine(ex.Message);
      throw;
   }
}

private object Deserialize(Type t, string xml)
{
   var serializer = new DataContractSerializer(t);

   var s = new MemoryStream(Encoding.UTF8.GetBytes(xml));
   return serializer.ReadObject(s);
}

private string Serialize(Type t, object dto)
{
   var serializer = new DataContractSerializer(t);

   using (var ms = new MemoryStream())
   {
      serializer.WriteObject(ms, dto);

      return Encoding.UTF8.GetString(ms.ToArray());
   }
}

It’s not much of a stretch to then use reflection to test all of your service layer DTOs for serialization data loss.

Advertisements