Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Customizing AutoFixure using FromSeed Causes Exception

Given the two classes:

class Foo
{
    ...
}

class Bar
{
    public Foo FooBar { get; set; }
}

I have set up the following test:

void Test()
{
    var fixture = new Fixture();

    fixture.Customize<Foo>(x => x.FromSeed(TestFooFactory));

    var fooWithoutSeed = fixture.Create<Foo>();
    var fooWithSeed = fixture.Create<Foo>(new Foo());

    var bar = fixture.Create<Bar>(); //error occurs here
}

Foo TestFooFactory(Foo seed)
{
    //do something with seed...

    return new Foo();
}

I can create Foo objects directly with and without seed values without any problem. But once I try to create a Bar object that has a Foo property, I get an ObjectCreationException:

The decorated ISpecimenBuilder could not create a specimen based on the request: Foo. This can happen if the request represents an interface or abstract class; if this is the case, register an ISpecimenBuilder that can create specimens based on the request. If this happens in a strongly typed Build expression, try supplying a factory using one of the IFactoryComposer methods.

I'd expect TestFooFactory to get passed a null seed value during the creation of Bar, just as when I created Foo without a seed value. Am I doing something wrong, or could this be a bug?

In my real-world scenario, I want to customize how AutoFixture would use seeded values for certain objects when I pass seeded values in, but I still want AutoFixture to default to normal behavior if no seed is provided.

like image 701
Nathan A Avatar asked Nov 10 '15 16:11

Nathan A


1 Answers

The way you're customizing the Fixture to use seed values is correct.

The behavior you're seeing is a consequence of how the FromSeed customization modifies the AutoFixture pipeline. If you're interested in reading up on the details, I've described them here.

As a workaround, you can use a custom specimen builder for seeded requests like this one:

public class RelaxedSeededFactory<T> : ISpecimenBuilder
{
    private readonly Func<T, T> create;

    public RelaxedSeededFactory(Func<T, T> factory)
    {
        this.create = factory;
    }

    public object Create(object request, ISpecimenContext context)
    {
        if (request != null && request.Equals(typeof(T)))
        {
            return this.create(default(T));
        }

        var seededRequest = request as SeededRequest;

        if (seededRequest == null)
        {
            return new NoSpecimen(request);
        }

        if (!seededRequest.Request.Equals(typeof(T)))
        {
            return new NoSpecimen(request);
        }

        if ((seededRequest.Seed != null)
            && !(seededRequest.Seed is T))
        {
            return new NoSpecimen(request);
        }

        var seed = (T)seededRequest.Seed;

        return this.create(seed);
    }
}

You can then use it to create objects of type Foo like this:

fixture.Customize<Foo>(c => c.FromFactory(
    new RelaxedSeededFactory<Foo>(TestFooFactory)));

This customization will pass default(Foo) – that is null – as a seed to the TestFooFactory factory function when populating properties of type Foo.

like image 147
Enrico Campidoglio Avatar answered Sep 19 '22 17:09

Enrico Campidoglio