Reputation: 481
So, I have a object with a lot of IEnumerable properties. In a unit test i want to do something like this:
var subsequentAgreement = _fixture.Build<Foo>()
.With(dto => dto.Bars,
_fixture.CreateMany<Bar>())
.Create();
And for the other IEnumerable<T>
properties i want a Enumerable.Empty<T>()
I have a ISpecimenBuilder
public class EmptyEnumerableBuilder : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
object returnObject = new NoSpecimen(request);
var type = request as Type;
if (type != null && type.IsGenericType)
{
var typeArguments = type.GetGenericArguments();
if(!typeArguments.Any() || typeof(IEnumerable<>) == type.GetGenericTypeDefinition())
returnObject = Array.CreateInstance(typeArguments.Single(), 0);
}
return returnObject;
}
}
which i add like so: _fixture.Customizations.Add(new EmptyEnumerableBuilder());
And that works just fine, except all of the other objects i create now have Empty enumerables.
I am looking for a way to apply this EmptyEnumerableBuilder
for a single _fixture.Build<>()
and leave the rest default, but i can't seem to find a way.
I have tried using a type limitation like so:
_fixture.Customize<SubsequentAgreementLimitationsDto>(composer => new EmptyEnumerableBuilder());
But strangely all other objects created by fixture still have empty enumerables
Upvotes: 4
Views: 2497
Reputation: 233367
If you need something convention-driven, you may be able to use Albedo to empty all writable IEnumerable<>
properties. You could start with something like this:
public class EmtpyEnumerables : ReflectionVisitor<object>
{
private object value;
public EmtpyEnumerables(object value)
{
this.value = value;
}
public override object Value
{
get { return value; }
}
public override IReflectionVisitor<object> Visit(PropertyInfoElement propertyInfoElement)
{
var pi = propertyInfoElement.PropertyInfo;
if (pi.PropertyType.IsConstructedGenericType &&
pi.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>) &&
pi.CanWrite)
{
var elementType = pi.PropertyType.GetGenericArguments().Single();
pi.SetValue(value, Array.CreateInstance(elementType, 0));
return this;
}
return base.Visit(propertyInfoElement);
}
}
Assuming that Foo
looks like this:
public class Foo
{
public IEnumerable<Bar> Bars { get; set; }
public IEnumerable<Baz> Bazs { get; set; }
public IEnumerable<Qux> Quxs { get; set; }
public string Corge { get; set; }
public int Grault { get; set; }
}
Then the following test passes:
[Fact]
public void FillBarsZeroOutAllOtherSequences()
{
var fixture = new Fixture();
var actual = fixture.Create<Foo>();
new TypeElement(actual.GetType()).Accept(new EmtpyEnumerables(actual));
actual.Bars = fixture.CreateMany<Bar>();
Assert.NotEmpty(actual.Bars);
Assert.Empty(actual.Bazs);
Assert.Empty(actual.Quxs);
Assert.NotEqual(default(string), actual.Corge);
Assert.NotEqual(default(int), actual.Grault);
}
If you think it's too much bother to write out new TypeElement(actual.GetType()).Accept(new EmtpyEnumerables(actual));
, I'm sure you can figure out to hide it in a helper method.
Upvotes: 2