Duncan Roosma
Duncan Roosma

Reputation: 481

AutoFixture Customization on single object creation

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

Answers (1)

Mark Seemann
Mark Seemann

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

Related Questions