So I have the following types:
public abstract class Base
{
public string Text { get; set; }
public abstract int Value { get; set; }
}
public class BaseImplA : Base
{
public override int Value { get; set; }
}
public class BaseImplB : Base
{
public override int Value
{
get { return 1; }
set { throw new NotImplementedException(); }
}
}
I want AutoFixture to alternate creating BaseImplA and BaseImplB when Base is requested.
var fixture = new Fixture().Customize(new TestCustomization());
var b1 = fixture.Create<Base>();
var b2 = fixture.Create<Base>();
The issue is BaseImplB throws a NotImplementedException from the Value property setter. So I created the following customization:
public class TestCustomization : ICustomization
{
private bool _flag;
private IFixture _fixture;
public void Customize(IFixture fixture)
{
_fixture = fixture;
fixture.Customize<BaseImplB>(composer =>
{
return composer.Without(x => x.Value);
});
fixture.Customize<Base>(composer =>
{
return composer.FromFactory(CreateBase);
});
}
private Base CreateBase()
{
_flag = !_flag;
if (_flag)
{
return _fixture.Create<BaseImplA>();
}
return _fixture.Create<BaseImplB>();
}
}
But what's happening is that the Value is not being set for BaseImplA or BaseImplB. Can anyone point out where I'm going wrong?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With