5

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.

Nathan A
  • 11,059
  • 4
  • 47
  • 63

1 Answers1

5

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.

Community
  • 1
  • 1
Enrico Campidoglio
  • 56,676
  • 12
  • 126
  • 154
  • That works like a charm! Thanks for the workaround. – Nathan A Nov 11 '15 at 14:39
  • 2
    I updated the custom `RelaxedSeededFactory` specimen builder with a _better_ approach. Instead of handling all requests, it will only handle seeded and non-seeded requests for `T`. – Enrico Campidoglio Nov 11 '15 at 14:56
  • 1
    Just as an update, AutoFixture 3.36.12 fixes this issue: https://github.com/AutoFixture/AutoFixture/commit/c7ce7d239a26fd7aef8c464ba214c366681d0886. Thanks @Enrico! – Nathan A Nov 20 '15 at 14:57