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:
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.
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);
}