Back in the days of AutoFixture 1.0 I occasionally got the feedback that although people liked the engine and its features, they didn't like the data it generated. I think they particularly didn't like all the Guids, but Håkon Forss suggested combining Object Hydrator's data generator with AutoFixture.

In fact, this suggestion made me realize that AutoFixture 1.0's engine wasn't extensible enough, which again prompted me to build AutoFixture 2.0. Now that AutoFixture 2.0 is out, what would be more fitting than to examine whether we can do what Håkon suggested?

It turns out to be pretty easy to customize AutoFixture to use Object Hydrator's data generator. The main part is creating a custom ISpecimenBuilder that acts as an Adapter of Object Hydrator:

public class HydratorAdapter : ISpecimenBuilder
{
    private readonly IMap map;
 
    public HydratorAdapter(IMap map)
    {
        if (map == null)
        {
            throw new ArgumentNullException("map");
        }
 
        this.map = map;
    }
 
    #region ISpecimenBuilder Members
 
    public object Create(object request,
        ISpecimenContext context)
    {
        var pi = request as PropertyInfo;
        if (pi == null)
        {
            return new NoSpecimen(request);
        }
 
        if ((!this.map.Match(pi))
            || (this.map.Type != pi.PropertyType))
        {
            return new NoSpecimen(request);
        }
 
        return this.map.Mapping(pi).Generate();
    }
 
    #endregion
}

The IMap interface is defined by Object Hydrator, ISpecimenBuilder and NoSpecimen are AutoFixture types and the rest are BCL types.

Each HydratorAdapter adapts a single IMap instance. The IMap interface only works with PropertyInfo instances, so the first thing to do is to examine the request to figure out whether it's a request for a PropertyInfo at all. If this is the case and the map matches the request, we ask it to generate a specimen for the property.

To get all of Object Hydrator's maps into AutoFixture, we can now define this customization:

public class ObjectHydratorCustomization :
    ICustomization
{
    #region ICustomization Members
 
    public void Customize(IFixture fixture)
    {
        var builders = from m in new DefaultTypeMap()
                        select new HydratorAdapter(m);
        fixture.Customizations.Add(
            new CompositeSpecimenBuilder(builders));
    }
 
    #endregion
}

The ObjectHydratorCustomization simply projects all maps from Object Hydrator's DefaultTypeMap into instances of HydratorAdapter and adds these as customizations to the fixture.

This enables us to use Object Hydrator with any Fixture instance like this:

var fixture = new Fixture()
    .Customize(new ObjectHydratorCustomization());

To prove that this works, here's a dump of a Customer type created in this way:

{
  "Id": 1,
  "FirstName": "Raymond",
  "LastName": "Reeves",
  "Company": "Carrys Candles",
  "Description": "Lorem ipsum dolor sit",
  "Locations": 53,
  "IncorporatedOn": "\/Date(1376154940000+0200)\/",
  "Revenue": 33.57,
  "WorkAddress": {
    "AddressLine1": "32373 BALL Lane",
    "AddressLine2": "29857 DEER PARK Dr.",
    "City": "fullerton",
    "State": "NM",
    "PostalCode": "27884",
    "Country": "GI"
  },
  "HomeAddress": {
    "AddressLine1": "66377 NORTH STAR Pl.",
    "AddressLine2": "33406 MAY Dr.",
    "City": "miami",
    "State": "MD",
    "PostalCode": "18361",
    "Country": "PH"
  },
  "Addresses": [],
  "HomePhone": "(388)538-1266",
  "Type": 0
}

Without Object Hydrator's data generators, this would have looked like this instead:

{
  "Id": 1,
  "FirstName": "FirstNamebf53cb4c-3aae-4963-bb0c-ad0219293736",
  "LastName": "LastName079f7ab2-d026-48c5-8cfb-76e0568d1d79",
  "Company": "Company9ffe4640-2534-4ef7-b066-fb6bbe3a668c",
  "Description": "Descriptionf5843974-b14b-4bce-b3cc-63ad6aaf3ab2",
  "Locations": 2,
  "IncorporatedOn": "\/Date(1290169587222+0100)\/",
  "Revenue": 1.0,
  "WorkAddress": {
    "AddressLine1": "AddressLine1f4d50570-423e-4a74-8348-1c54402ffe48",
    "AddressLine2": "AddressLine2031fe3e2-40c1-4ec3-b445-e88c213457e9",
    "City": "Citycd33fce3-66bb-457d-8f99-98a16c0c5bf1",
    "State": "State40bebd6d-6073-4421-8a74-e910ff9d09e3",
    "PostalCode": "PostalCode1da93f22-799b-4f6b-a5ce-f4816f8bbb05",
    "Country": "Countryfa2ad951-ce0c-42a4-ab55-c077b6e03f00"
  },
  "HomeAddress": {
    "AddressLine1": "AddressLine145cbffeb-d7a9-4778-b297-d010c30b7614",
    "AddressLine2": "AddressLine2e86d6476-5bdc-4940-a8ee-975bf3f65d49",
    "City": "City6ae3aab9-7c73-4768-ae7d-a6ea515c816a",
    "State": "State56de6222-fd84-46b0-ace0-c6098dbd0681",
    "PostalCode": "PostalCodeca1af9af-a97b-4966-b156-cfbebd6d5e38",
    "Country": "Country6960eebe-fe6f-4b63-ad73-7ba6a2b95791"
  },
  "Addresses": [],
  "HomePhone": "HomePhone623f9d6f-febe-4c9f-87f8-e90d7e57eb46",
  "Type": 0
}

One limitation of Object Hydrator is that it requires the classes to have default constructors. AutoFixture doesn't have that constraint, and to prove that I defined the only available Customer constructor like this:

public Customer(int id)

With AutoFixture, this is not a problem and the Customer instance is created as described above.

With the extensibility model of AutoFixture 2.0 I am pleased to be able to verify that Håkon Forss (and others) can now have the best of both worlds :)


Comments

Almir Begovic #
It does not compile, I had to make a slight change to make it work:

public class ObjectHydratorCustomization :
ICustomization
{
#region ICustomization Members

public void Customize(IFixture fixture)
{
var builders= (from m in new DefaultTypeMap()
select new HydratorAdapter(m) as ISpecimenBuilder);
fixture.Customizations.Add(
new CompositeSpecimenBuilder(builders));
}

#endregion
}
2012-02-16 14:38 UTC
Ryan Smith #
I moved ObjectHydrator from Codeplex to GitHub

Here it is.
As always feel free to use any part you'd like.'
2014-06-17 04:22 UTC


Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or somewhere else with a permalink. Ping me with the link, and I may respond.

Published

Monday, 22 November 2010 06:42:37 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!
Published: Monday, 22 November 2010 06:42:37 UTC