Autofixture: Building a customized instance fails on circular references

Created on 13 Jan 2018  ·  5Comments  ·  Source: AutoFixture/AutoFixture

    [TestFixture]
    public class TestAutoFixture4
    {
        [Test]
        [AutoMoqData]
        public void CreatingAnDummyObjectShouldNotThrow(IFixture fixture)
        {
            fixture.Invoking(x => x.Create<DummyObject>()).ShouldNotThrow();
        }

        [Test]
        [AutoMoqData]
        public void BuildingAnDummyObjectShouldNotThrow(IFixture fixture)
        {
            fixture.Invoking(x => x.Build<DummyObject>().Create()).ShouldNotThrow();
        }
    }

    public class DummyObject
    {
        public Guid Id { get; set; }
        public DummyObject CircularRef { get; set; }
    }

    public class AutoMoqDataAttribute : AutoDataAttribute
    {
        public AutoMoqDataAttribute()
            : base(new Fixture()
                 .Customize(
                    new DummyCustomization()
                ))
        { }
    }

    public class DummyCustomization : ICustomization
    {
        public void Customize(IFixture fixture)
        {
            fixture.Customize<DummyObject>(c =>
                c.Without(x => x.CircularRef)
            );
        }
    }

Just updated AutoFixture from 3.50.6 to 4.0.0.

The above code works perfectly on 3.50.6, but only the first test (using fixture.Create) passes using 4.0.0 (.NET 4.6.2, NUnit3.7.1, FluentAssertions 4.19.4).

The test using fixture.Build fails, even if the injected customization specifies not to resolve the circular reference.

Did I miss something in the changelog or is it a bug ?

Thanks in advance :)

question

Most helpful comment

Do you think the Omitter approach can solve the problem ? I didn't know this class and will definitely try it anyway.

If you rewrite your customization as following, all the tests will start to pass:

c# public class DummyCustomization : ICustomization { public void Customize(IFixture fixture) { // Don't populate the DummyObject.CircularRef property. fixture.Customizations.Add( new Omitter( new EqualRequestSpecification( typeof(DummyObject).GetProperty(nameof(DummyObject.CircularRef))))); } }

Therefore, yep, it should fix your issue 😉 Also this approach doesn't look too cumbersome, so should be acceptable.

Please let me know if you have further questions on your question.

All 5 comments

@Dev-I-Ant Thanks for posting the issue here and for such a detailed description 👍

Well, this behavior is a result of the changes applied in #781. I've fixed the issue when one customization could affect other customizations. That happens even if you have two subsequent customizations for the same type - the previous one will be entirely ignored.

When you use the Build<T>() API, you are in fact creating a temporary customization for the DummyObject type. That means that all the previous customizations (applied via the Customize<DummyObject>() API) are ignored and don't participate. That's why you again see error about circular dependency.

This kind of isolation is required, otherwise very weird issues could occur (see the referenced issues in the PR mentioned above). Therefore, to keep things simple, I'd say that the observed behavior is not a bug, rather a particularity of the design we currently have.


To mitigate the issue you observe, I see basically two ways.

Workaround 1: Add Omitter for the property

We had already a similar question, so you could re-use the approach suggested here or alter it to better fit your needs. Obviously, if that is a common scenario in your project, you could create an extension method to achieve the task more easily.

Workaround 2: Use the Create<T>() API instead

In your sample there is no need to use the Build<> API. Of course, it might be that you provided a simplified sample and actually you need that API. However, I decided to provide you with this option just in case 😃

Let me know whether my answer helped to shed some light onto this issue 😉

@zvirja Thanks for your insights, this makes things clearer.

I already read a lot about the fact that stacking customizations on the same type is not possible (even if frustrating), and this change will certainly give me some trouble, but I understand why you had to change something.

My use case is testing an application using a large graph of objects which many circular references; the typical (anti-?)pattern where a parent holds a list of children which in turn reference their parent.

I'd love to change the model but it's not possible, at least for now.

So simply creating objects using fixture.Create is not an option and we usually build them calling OmitAllProperties just after, which limits the power of AF.

So I used to create some customizations that handled that for me, omitting the circular references (then the devs just had to build their objects without having to care on whether there were circular references or not, and being able to further customize them using With, Without and so on). But this change kind of breaks all this.
Do you think the Omitter approach can solve the problem ? I didn't know this class and will definitely try it anyway.

Thanks again, and please let me know if you think you can add something to my reflection.

Do you think the Omitter approach can solve the problem ? I didn't know this class and will definitely try it anyway.

If you rewrite your customization as following, all the tests will start to pass:

c# public class DummyCustomization : ICustomization { public void Customize(IFixture fixture) { // Don't populate the DummyObject.CircularRef property. fixture.Customizations.Add( new Omitter( new EqualRequestSpecification( typeof(DummyObject).GetProperty(nameof(DummyObject.CircularRef))))); } }

Therefore, yep, it should fix your issue 😉 Also this approach doesn't look too cumbersome, so should be acceptable.

Please let me know if you have further questions on your question.

This works perfectly. Thanks a lot! I guess you can close this then :)

@Dev-I-Ant Awesome, thanks for the feedback! :)

Was this page helpful?
0 / 5 - 0 ratings