Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
776 views
in Technique[技术] by (71.8m points)

c# - How to customize AutoFixture for a default creation of a complex object?

I have a pretty big class using entity framework with some list properties as a one to many relationship. I use AutoFixture to create test data and to get rid of circular references I set a lot of 'Without'. But keep copying this same code again and again. I cant figure out how to customize the initial creation. It looks something like this

Works:

  var entity = fixture.Build<MyObject>()
                .Without(c => c.Cars)
                .Without(c => c.Boats)
                .Without(c => c.Motorcycles)
                .Without(c => c.Skateboards)
                .Create();

I would like setup these as defaults for every time I use a fixture to create a MyObject (and then be able to override them in some cases)

I tried creating a class like this

public class Conventions: ICustomization
{
    public void Customize(IFixture fixture)
    {

        fixture.Customize<MyObject>
        (c => c
                .Without(c => c.Cars)
                .Without(c => c.Boats)
                .Without(c => c.Motorcycles)
                .Without(c => c.Skateboards)
        );

And then use it:

var fixture = new Fixture();

fixture.Customize(new Conventions());
var entity = fixture.Build<MyObjec>().Create();

But nope it fails on recursive declarations

question from:https://stackoverflow.com/questions/65903107/how-to-customize-autofixture-for-a-default-creation-of-a-complex-object

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

The behavior you describe is intended by design. The .Build<T>() customization creates a one-time-use customization that ignores all previously assigned customizations.

If the intention was to just create an instance using a previously applied type customization, then all you need to do is remove the .Build<T>() statement from you test and just use .Create() directly. This will avoid creating the customization from scratch.

var fixture = new Fixture();

fixture.Customize(new Conventions());
var entity = fixture.Create<MyObject>();

Since you are using EntityFramework it is likely that your navigation properties are marked as virtual. You could write a IRequestSpecification that would identify virtual members and use the Omitter to omit all navigation properties.

Another approach to tackle your issue would be to consider changing the design of your domain models. Ask yourself whether it is a valid operation to allow clients of your domain model to "set/replace" the collection of boats for example. If the answer is "no" you might want to change the model so that this rule is reflected in the design itself, i.e. make the setter private and change the collection to a read only collection. If you do this AutoFixture will ignore the navigation properties and you would be able to set their values using the appropriate business rules.

Finally if you still want to reuse a parts of the customization you could extend AutoFixture in a way that would allow this kind of operation. Both Customize<T>() and Build<T>() are employing the IPostprocessComposer<T> interface to compose the inline customizations. You could write an extension method that intervenes in the process and delegates the customization to a third party.

public static class ComposerExtensions
{
    public static IPostprocessComposer<T> Using<T>(this IPostprocessComposer<T> source, IPartialPostprocessComposer<T> composer)
    {
        return composer.Compose(source);
    }
}

Now assuming a third party could look like this.

public class MyClassPartialComposer : IPartialPostprocessComposer<MyClass>
{
    public IPostprocessComposer<MyClass> Compose(IPostprocessComposer<MyClass> source)
    {
        return source
            .Without(x => x.Cars)
            .Without(x => x.Boats);
    }
}

Your customization could look like this.

var actual = fixture.Build<MyClass>()
    .Using(new MyClassPartialComposer())
    .With(x => x.Name, "hello")
    .Create();

With this approach you could also compose parts of the inline customization.

Remember though that this last approach is not supported officially by AutoFixture and you'd have to define the extension method and the partial composer implementations yourself.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...