How to use AutoFixture to build with customized properties while keeping type customizations?
Asked Answered
D

2

29

I am trying to use autofixture to create an object but there are certain properties that I want to always be defaulted (while the rest could be auto generated). However, whenever I setup an customization it gets overwritten when I build with customizations.

void Main()
{
    var fixture = new Fixture();
    fixture.Customize<Person>(composer => composer.With(p => p.Name, "Ben"));

    var person = fixture.Build<Person>()
        .With(p => p.DateOfBirth, new DateTime(1900, 1, 1))
        .Create();

    /*  RESULT OF person below
    Name    null
    DateOfBirth 1/1/1900
    StreetAddress   StreetAddressafd6b86b-376a-4355-9a9c-fbae34731453
    State   State019e867b-ac5e-418f-805b-a64146bc06bc
    */
}

public class Person
{
    public string Name { get; set;}

    public DateTime DateOfBirth { get; set;}

    public string StreetAddress { get; set;}

    public string State { get; set;}
}

The Name and DateOfBirth property customizations do not conflict so I don't know why Name ends up being null. I would expect name to be Ben.

How can I get it so both customizations are applied (ie. Name = "Ben" and DateOfBirth = 1/1/1900)?

Duad answered 31/7, 2016 at 22:34 Comment(0)
A
40

As @DavidOsborne correctly pointed out, the behavior you are seeing is as designed.

A better approach is to organize your customizations in separate classes and then enable them as needed by a specific test scenario.

A customization object implements the ICustomization interface and its job is to configure the Fixture object in a specific way. Here's an example:

public class AllPersonsAreNamedBen : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customize<Person>(composer =>
            composer.With(p => p.Name, "Ben"));
    }
}

public class AllPersonsAreBornIn1900 : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customize<Person>(composer =>
            composer.With(p => p.DateOfBirth, new DateTime(1900, 1, 1)));
    }
}

You can enable a customization on a specific Fixture by using the Customize method, for example:

fixture.Customize(new AllPersonsAreNamedBen());

or:

fixture.Customize(new AllPersonsAreBornIn1900());

You can also combine multiple customizations into a new one by using the CompositeCustomization class:

public class AllPersonsAreNamedBenAndAreBornIn1900 : CompositeCustomization
{
    public AllPersonsAreNamedBenAndAreBornIn1900()
        : base(new AllPersonsAreNamedBen(),
               new AllPersonsAreBornIn1900())
    {
    }
}

at which point you can simply say:

fixture.Customize(new AllPersonsAreNamedBenAndAreBornIn1900());

However, keep in mind that the order in which the customizations are applied on a Fixture matters: the last one wins and can potentially override the previous ones, as @MarkSeemann pointed out in the comments. This, too, is by design.

So, while you can combine existing customizations that work on different types, in this particular case, since both customizations target the same type, you'll have to create a new customization to encapsulate all the settings for the Person type combined:

public class AllPersonsAreNamedBenAndAreBornIn1900 : CompositeCustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customize<Person>(composer =>
            composer.With(p => p.Name, "Ben")
                    .With(p => p.DateOfBirth, new DateTime(1900, 1, 1)));
    }
}

As a general rule, keeping your customizations small and focused enables you to reuse them in different tests, combining them for specific test scenarios.

Appointor answered 1/8, 2016 at 9:4 Comment(4)
Isn't AllPersonsAreNamedBen going to override AllPersonsAreBornIn1900?Yakka
I stand corrected. Since both customizations work on the same type, they absolutely will. Thanks for calling it out! This is one aspect of AutoFixture that bites me from time to time – I wish I could combine multiple customizations incrementally like that so bad that I sometimes forget it's not possible.Appointor
Downvoted because it is misleading - you cannot combine two customizations that customize one type. It would be nice to update this answer since it has the most upvotes.Unriddle
@Unriddle I explictly wrote: "So, while you can combine existing customizations that work on different types, in this particular case, since both customizations target the same type, you'll have to create a new customization to encapsulate all the settings for the Person type combined". How is it misleading?Appointor
Y
13

This looks like it's by design:

Note that the Build method chain is best understood as a one-off Customization. It bypasses all Customizations on the Fixture instance. Instead, it allows fine-grained control when building a specific specimen. However, in most cases, adding a convention-based ICustomization is a better, more flexible option.

...from the Build() method's documentation.

I appreciate that this is probably not an ideal answer. However, the documentation does provide a hint as to how you might arrive at one.

Yerkovich answered 1/8, 2016 at 8:28 Comment(1)
I would so much like Ploeh to explain the infamous "However, in most cases, adding a convention-based ICustomization is a better, more flexible option." How can it be more flexible than being able to combine the 2? Then we could simply have a .BypassAllCustomizations() when we want to start fresh... I can't make sense of this decision they took back then...Turnaround

© 2022 - 2024 — McMap. All rights reserved.