With AutoFixture 3.18.5+, this isn't too difficult to do. There's at least two different issues in play here:
Dealing with BaseImplB
The BaseImplB
class needs special treatment, which is quite easy to deal with. You only need to instruct AutoFixture to ignore the Value
property:
public class BCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<BaseImplB>(c => c.Without(x => x.Value));
}
}
This omits the Value
property, but otherwise creates instances of BaseImplB
as usual, including filling out any other writable properties, such as the Text
property.
Alternating between different implementation
In order to alternate between BaseImplA
and BaseImplB
, you can write a Customization like this:
public class AlternatingCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(new AlternatingBuilder());
}
private class AlternatingBuilder : ISpecimenBuilder
{
private bool createB;
public object Create(object request, ISpecimenContext context)
{
var t = request as Type;
if (t == null || t != typeof(Base))
return new NoSpecimen(request);
if (this.createB)
{
this.createB = false;
return context.Resolve(typeof(BaseImplB));
}
this.createB = true;
return context.Resolve(typeof(BaseImplA));
}
}
}
It simply deals with requests for Base
, and relays alternating requests for BaseImplA
and BaseImplB
to the context
.
Packaging
You can package up both Customizations (and others, if you have them) in a Composite, like this:
public class BaseCustomization : CompositeCustomization
{
public BaseCustomization()
: base(
new BCustomization(),
new AlternatingCustomization())
{
}
}
This will enable you to request BaseImplA
, BaseImplB
, and Base
, as you need them; the following tests demonstrate this:
[Fact]
public void CreateImplA()
{
var fixture = new Fixture().Customize(new BaseCustomization());
var actual = fixture.Create<BaseImplA>();
Assert.NotEqual(default(string), actual.Text);
Assert.NotEqual(default(int), actual.Value);
}
[Fact]
public void CreateImplB()
{
var fixture = new Fixture().Customize(new BaseCustomization());
var actual = fixture.Create<BaseImplB>();
Assert.NotEqual(default(string), actual.Text);
Assert.Equal(1, actual.Value);
}
[Fact]
public void CreateBase()
{
var fixture = new Fixture().Customize(new BaseCustomization());
var actual = fixture.CreateMany<Base>(4).ToArray();
Assert.IsAssignableFrom<BaseImplA>(actual[0]);
Assert.NotEqual(default(string), actual[0].Text);
Assert.NotEqual(default(int), actual[0].Value);
Assert.IsAssignableFrom<BaseImplB>(actual[1]);
Assert.NotEqual(default(string), actual[1].Text);
Assert.Equal(1, actual[1].Value);
Assert.IsAssignableFrom<BaseImplA>(actual[2]);
Assert.NotEqual(default(string), actual[2].Text);
Assert.NotEqual(default(int), actual[2].Value);
Assert.IsAssignableFrom<BaseImplB>(actual[3]);
Assert.NotEqual(default(string), actual[3].Text);
Assert.Equal(1, actual[3].Value);
}
A note on versioning
This question surfaced a bug in AutoFixture, so this answer will not work unmodified in versions of AutoFixture prior to AutoFixture 3.18.5.
A note on design
AutoFixture was originally build as a tool for Test-Driven Development (TDD), and TDD is all about feedback. In the spirit of GOOS, you should listen to your tests. If the tests are hard to write, you should consider your API design. AutoFixture tends to amplify that sort of feedback, and that also seems to be the case here.
As given in the OP, the design violates the Liskov Substitution Principle, so you should consider an alternative design where this is not the case. Such an alternative design is also likely to make the AutoFixture setup simpler, and easier to maintain.
Fixture
instance as:fixture.Customize(new TestCustomization());
After doing this, no exception should be thrown. – CelliniValue
forBaseImplB
, do you? Am I guessing correctly that you also want theText
property to be populated? – Felty