Search code examples
c#tddautofixture

AutoFixture: How to use a common test data set for the generation of different objects?


I would like to test a self written XML parser that takes, well an XML string and returns the model representation of that.

T Parse(string content);

The issue I am having is regarding the assertion part of my test. Because each time I call Create<T>() it generates new random data, which is not what I want in that case. I kind of need a common testdata set that i can use in the following order:

a) Generate XML string that can be passed to my parser

b) Generate model representation using the same test data set

c) Compare XML parser results with the generated model representation and Assert.AreEqual()

I came across the Freeze<T>() method which "sounds" like it could fit my purpose. However I have no idea on how to use it.

So the question is: How can I use a common testdata set for the generation of different objects?

This is my current approach and static test data generator class.

public static class TestDataGenerator
{
    public static string GenerateSyntheticXmlTestData<T>(int minOid, int maxOid, int amount = 5)
    {
        var fixture = new Fixture()
        {
            RepeatCount = amount
        };

        fixture.Customizations.Add(new OidGenerator(minOid, maxOid));
        fixture.Customizations.Add(new EnableAllProperties());

        var testData = fixture.Create<T>();

        var serializedXmlTestData = XmlSerializerHelper.Current.SerializeToXmlDocument(testData, Encoding.UTF8);

        return serializedXmlTestData;
    }

    public static ICollection<T> GenerateSyntheticModelTestData<T>(int minOid, int maxOid, int amount = 1)
    {
        var fixture = new Fixture()
        {
            RepeatCount = 1
        };

        fixture.Customizations.Add(new OidGenerator(minOid, maxOid));

        var testData = fixture.CreateMany<T>(amount).ToList();

        return testData;
    }
}

And this is they way I would like to test the parser. I hope its clear what I am trying to achieve.

[Fact]
public void ShouldParse()
{
    // [...]
    var xmlContent = TestDataGenerator.GenerateSyntheticXmlTestData<MyType>(minOid: 1, maxOid: 100, amount: 5);

    // Here I would like to generate a model object using the same data
    //
    // var modelContent = new Fixture().Create<ModelType>(); 

    var parsedContent = parser.Parse(xmlContent);

    //parsedContent.Should().BeEquivalentTo(modelContet); 
}

Solution

  • When testing parsers, I often find it easiest to take a page from the property-based testing playbook. Many of the techniques useful for property-based testing are also useful with AutoFixture.

    When doing property-based testing of parsing logic, it's often useful to define a serializer that goes with the parser. That is, a function that can turn a given model into the format that the parser parses. In this case, it would be an XML serializer.

    It's often much easier to instruct AutoFixture (or a property-based testing library) to create valid instances of 'model' objects, rather than instructing it to produce valid XML strings.

    Once you've set up AutoFixture to do that, you let it create instances of your model, then serialize the model, and let the parser parse the serialized model. The assertion is that the parsed model should be equal to the input model.

    Scott Wlaschin calls this test pattern There and back again. You can also see an example of it on my blog, using FsCheck.

    With AutoFixture, it might look something like this:

    [Fact]
    public void RoundTrippingWorks()
    {
        var fixture = new Fixture().Customize(/*...*/);
        var model = fixture.Create<MyModel>();
    
        string xml = MyXmlSerializer.Serialize(model);
        MyModel actual = MyXmlParser.Parse(xml);
    
        Assert.Equal(model, actual);
    }
    

    (I haven't tried to compile that, so there could be typos...)