Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is AutoFixture Customization causing inherited properties to not be filled?

I wrote the following customization and have it applied as part of a composite on most of my tests. My entities have a read-only Id, but I'm using their SetId method in this customization to make sure all entities have some Id if they are transient (don't have an Id already).

public class SetEntityIdCustomization : ICustomization {
    public void Customize(IFixture fixture) {
        var engine = ((Fixture)fixture).Engine;
        fixture.Customizations.Add(new Postprocessor(
            engine, o => {
                var entity = o as BaseEntity;
                if (entity == null || !entity.IsTransient()) {
                    return;
                }
                entity.SetId(fixture.CreateAnonymous<Guid>());
            }));
    }
}

This has been working great, until I discovered a very odd thing today. If I feed a test one of my entities that directly inherits from BaseEntity, all is well and it's writeable properties are auto-filled. However, if I ask for an entity that inherits from something further down from BaseEntity, my customization prevents the properties from auto-filling.

The User entity in this test method is filled properly:

public class User : BaseEntity {
    public string Email { get; set; }
    public int CoolThings { get; set; }
}

...
[Theory, AutoDomainData]
public void SomeTest(User user, ...) {
    // user.Email and user.CoolThings have auto-filled values, as expected.
    ...
}

However, the AwesomeUser entity in the following test does not get any of the same properties auto-filled.

public class AwesomeUser : User {
    ...
}

...
[Theory, AutoDomainData]
public void SomeOtherTest(AwesomeUser user, ...) {
    // user.Email nor user.CoolThings have auto-filled values. What gives?
    ...
}

In both test cases, the Id property is auto-filled because of my customization. If I remove my customization, the SomeOtherTest's AwesomeUser instance gets its inherited properties auto-filled just fine. I must assume that my customization is what is messing things up.

Is there a better way to get all my BaseEntity instances to set their Id, or is there something else I'm missing with AutoFixture? I've applied my customization first, in the middle, and last, to no avail.

like image 803
ventaur Avatar asked Feb 01 '13 02:02

ventaur


1 Answers

The solution provided above is a pretty clever attempt, but not something I've seen before. A more idiomatic solution would be something like this:

public void Customize(IFixture fixture)
{
    fixture.Customizations.Add(
        new FilteringSpecimenBuilder(
            new Postprocessor(
                new BaseEntityBuilder(
                    new ConstructorInvoker(
                        new ModestConstructorQuery())),
                new AutoPropertiesCommand().Execute),
            new BaseEntitySpecification()));
}

private class BaseEntityBuilder : ISpecimenBuilder
{
    private readonly ISpecimenBuilder builder;
    private readonly IRequestSpecification specification;

    public BaseEntityBuilder(ISpecimenBuilder builder)
    {
        this.builder = builder;
        this.specification = new BaseEntitySpecification();
    }

    public object Create(object request, ISpecimenContext context)
    {
        if (!this.specification.IsSatisfiedBy(request))
            return new NoSpecimen(request);

        var b = (BaseEntity)this.builder.Create(request, context);
        b.SetId((Guid)context.Resolve(typeof(Guid)));
        return b;
    }
}

private class BaseEntitySpecification : IRequestSpecification
{
    public bool IsSatisfiedBy(object request)
    {
        var t = request as Type;
        if (t == null)
            return false;

        if (!typeof(BaseEntity).IsAssignableFrom(t))
            return false;

        return true;
    }
}

As you can see, this isn't a simple one-liner, which is indicative of AutoFixture being a rather opinionated library. In this case, AutoFixture's opinion is:

Favor object composition over class inheritance.

-Design Patterns, p. 20

AutoFixture is first and foremost a TDD tool, and one of the main advantages of TDD is that it provides feedback about class design. In this case, the feedback is: Inheritance is awkward and troublesome. Reconsider the design.

like image 73
Mark Seemann Avatar answered Apr 27 '23 23:04

Mark Seemann