Autofixture: Applying customizations from base class or interface fails in 4.0.0-rc1 (regression)

Created on 20 Nov 2017  ·  10Comments  ·  Source: AutoFixture/AutoFixture

The following tests succeed in AutoFixture 3.51.0 but fail in 4.0.0-rc1:
(even in the latest commit in master: c856cd6)

``` c#
[Fact]
public void ShouldApplyCustomizationsFromBaseClass()
{
var fixture = new Fixture();
fixture.Customize(c => c.Without(bc => bc.Id));
var res = fixture.Create();
Assert.Equal(0, res.Id);
}

[Fact]
public void ShouldApplyCustomizationsFromInterface()
{
var fixture = new Fixture();
fixture.Customize(c => c.Without(bc => bc.Id));
var res = fixture.Create();
Assert.Equal(0, res.Id);
}

public interface IInterface
{
int Id { get; set; }
}

public class BaseClass
{
public int Id { get; set; }
}

public class ImplClass : BaseClass, IInterface
{

}
```

question

All 10 comments

Thanks for raising the question. Well, this is the desired change and it has been applied on purpose.

Previously we had an issue when customization of one sub-type could affect other sub-type. For instance:

```c#
class Base { public string Common { get; set; } }
class Child1: Base { }
class Child2: Base { }

[Fact]
public void EnsureCustomizationAreNotAffected()
{
var fixture = new Fixture();
fixture.Customize(c => c.With(x => x.Common, "dummy"));

var result = fixture.Create<Child2>();

Assert.NotNull(result.Common);

}


In the v3 this test failed, causing a lot of confusion to people. As you might imagine, the scenarios were more complicated and it was very non-obvious why the particular members are not initialized. 

For instance, the following code will not work, which again proves that API is not designed for that.
```c#
fixture.Customize<Base>(c => c.With(x => x.Common, "foo"));

Therefore, in order to fix that issue we changed the approach, so that now customization of one type cannot affect other types, even if they belong to the same inheritance line. As you might see in that PR, it fixed a bunch of usability issues and added more clarity (while the change is indeed breaking).

If you still would like to omit base/interface properties, you could use the following snippet:
```c#
private class SamePropertySpecification : IRequestSpecification
{
private readonly Type _declaringType;
private readonly string _name;

public SamePropertySpecification(Type declaringType, string name)
{
    _declaringType = declaringType;
    _name = name;
}

public bool IsSatisfiedBy(object request)
{
    if (request is PropertyInfo pi)
    {
        return pi.DeclaringType == this._declaringType &&
               pi.Name.Equals(this._name, StringComparison.Ordinal);
    }

    return false;
}

}

[Fact]
public void TestBasePropertyOmitting()
{
var fixture = new Fixture();

fixture.Customizations.Add(new Omitter(new SamePropertySpecification(typeof(Base), nameof(Base.Common))));

var result = fixture.Create<Child1>();
Assert.Null(result.Common);

}
```

If you need that feature frequently, you could create your own extension method for fixture. However, I'd vote to not have such a feature out-of-the-box, as it looks very confusing.

Ok, good to know it was intentional. I definitely agree that it was confusing, but if it can be applied explicitly it's a nice feature. Will try your snippet (and will probably make the extension method).

By the way: Is this something you want to add to the breaking changes list? Might prevent the same question being asked over.

There is no way to opt-in to the old behaviour for either one customization of all customizations, right?

Is this something you want to add to the breaking changes list?

Well, it's mentioned in the bug fixes section. This is in fact not a breaking change, as the previous behavior was never stated as a "feature". Rather, that was an undesired side-effect and it has been finally eliminated 😉

There is no way to opt-in to the old behaviour for either one customization of all customizations, right?

No, there is no such a way I believe as I totally reworked the approach.

Please test the snippet and if works - feel free to close the issue. Or let me know if you have further questions 😃

I’ve tweaked it so it also works for interfaces and made an extension method so it can be used in ‘Fixture.Customize(...)’. Feels a bit more hacky than I’d like to, especially since the way it worked in v3 was perfect for us. But since our only use-case is omitting properties and it (most importantly 😉 ) works it’ll do the trick. Thanks!

@nphmuller Sorry that I've broken your neat and tidy code and now you need to survive 😄 But I believe that your sacrifice was made for the good of all of us, so you will not blame us too much 😅

No blame at all. Understand your reasoning perfectly and even agree. Just sucks for me 😉

Might dive into the problem later to see of there’s a neater way. For now its fine.

Just out of curiosity - what is your real use case for that? Because I never met a need in such kind of functionality.. Usually, when I write a test, I need to customize only specific type, rather than configure base or interface type. Are writing some kind of global customization?

In this case we use AutoFixture as a test data generator for Entity Framework integration tests.

We have a couple of base types or interfaces that our entities use that contain common properties. Id, tenant-id (and navigation property), stuff like that.

For example, one of these properties is which user created the object. This property is not that interesting for most tests, and if it was generated it would create a huge graph (the User class also has many navigation properties) that would have to be generated and inserted into the database. This would make the test slow or even fail at db level because the object wasn’t meant to be inserted that way.

So it’s easier to simply exclude these properties and opt-in when necessary. At base class level, because else the same omit rule would have to be written for every entity type.

@nphmuller Thanks for clarification, now it's clear 😉 Yep, it looks the scenario makes sense, but I must admit it looks like something you need pretty rarely.

Was this page helpful?
0 / 5 - 0 ratings