Search code examples
autofixture

Omit property of base class for all implementations in Autofixture


I have classes for example as follows:

public class BaseItem
{
    public Guid Id { get; set; }

    public Language Language { get; set; }
}

public class ItemA : BaseItem
{
    public string Name { get; set; }
}

public class ItemB : BaseItem
{
    public string Path { get; set; }
}

I want to omit the property Language everytime I create ItemA, ItemB or any future implementations of BaseItem class.

I added this line (below) in my customization class but in the generation of ItemA/ ItemB, AutoFixture still attempts to generate the Language property.

fixture.Register<BaseItem>(() => { return fixture.Build<BaseItem>().Without(i => i.Language).Create(); });

Omitting the property on implementation class level like this (see below) works.

fixture.Register<ItemA>(() => { return fixture.Build<ItemA>().Without(i => i.Language).Create(); });

My questions are:

  1. How can I implement a generic way to do this so I don't have to customize each implementation class?
  2. If there's a way to do #1, how can I override it in case I want the Language property to be generated for some exception?

Solution

  • Q1

    In order to target (or, rather, omit) a property defined on a base class, I know of no better way than defining a custom ISpecimenBuilder, in this case something like:

    public class OmitLanguageBuilder : ISpecimenBuilder
    {
        public object Create(object request, ISpecimenContext context)
        {
            var pi = request as PropertyInfo;
            if (pi != null &&
                pi.PropertyType == typeof(Language)
                && pi.Name == "Language"
                && pi.DeclaringType == typeof(BaseItem))
                return new OmitSpecimen();
    
            return new NoSpecimen();
        }
    }
    

    This specifically handles requests for the Language property only declared on BaseItem. If you want to be less specific, you can remove some of the predicates in the if expression. When AutoFixture gets such a request, it'll interpret the special 'signal type' OmitSpecimen as an instruction to ignore that property.

    You can register OmitLanguageBuilder with a Fixture instance directly (although you should package it in a Customization. This test passes:

    [Fact]
    public void Q1A()
    {
        var fixture = new Fixture();
        fixture.Customizations.Add(new OmitLanguageBuilder());
    
        var actual = fixture.Create<ItemA>();
    
        Assert.Null(actual.Language);
        Assert.NotEqual(default(Guid), actual.Id);
        Assert.NotNull(actual.Name);
    }
    

    A similar test for ItemB passes as well.

    Q2

    When you want to populate Language in special test cases, the easiest solution may simply be to populate it after the fact:

    [Fact]
    public void Q2SetAfter()
    {
        var fixture = new Fixture();
        fixture.Customizations.Add(new OmitLanguageBuilder());
    
        var actual = fixture.Create<ItemA>();
        actual.Language = fixture.Create<Language>();
    
        Assert.NotNull(actual.Language);
        Assert.NotEqual(default(Guid), actual.Id);
        Assert.NotNull(actual.Name);
    }
    

    Another option is to use the Build API:

    [Fact]
    public void Q2Build()
    {
        var fixture = new Fixture();
        fixture.Customizations.Add(new OmitLanguageBuilder());
    
        var actual = 
            fixture.Build<ItemA>().With(x => x.Language, fixture.Create<Language>()).Create();
    
        Assert.NotNull(actual.Language);
        Assert.NotEqual(default(Guid), actual.Id);
        Assert.NotNull(actual.Name);
    }
    

    And finally, if you need to change how AutoFixture handles the type in general, you can do something like this:

    [Fact]
    public void Q2Customize()
    {
        var fixture = new Fixture();
        fixture.Customizations.Add(new OmitLanguageBuilder());
        fixture.Customize<ItemA>(c => c.With(x => x.Language, fixture.Create<Language>()));
    
        var actual = fixture.Create<ItemA>();
    
        Assert.NotNull(actual.Language);
        Assert.NotEqual(default(Guid), actual.Id);
        Assert.NotNull(actual.Name);
    }