Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Customizing AutoFixure using FromSeed Causes Exception #467

Closed
NArnott opened this issue Nov 10, 2015 · 4 comments
Closed

Customizing AutoFixure using FromSeed Causes Exception #467

NArnott opened this issue Nov 10, 2015 · 4 comments
Labels

Comments

@NArnott
Copy link

NArnott commented Nov 10, 2015

Given 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.

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.

@ploeh
Copy link
Member

ploeh commented Nov 10, 2015

Crossposted on Stack Overflow: http://stackoverflow.com/q/33635042/126014

@ecampidoglio
Copy link
Member

tl;dr

The problem is that the factory function specified through IFactoryComposer<T>.FromSeed becomes exclusive for requests of type T but is only allowed to handle seeded requests, which isn't the case when populating properties and fields.

Background

When customizing a type with the IFactoryComposer<T>.FromSeed method, AutoFixture inserts a specimen builder called SeededFactory<T> into the pipeline. The responsibility of SeededFactory<T> is to look for SeededRequest objects whose requested type matches T and to relay them to a custom factory function passing the seed value as argument. All other requests are handled by returning NoSpecimen.

When AutoFixture populates the members of a class (such as properties and fields) it unwraps a SeededRequest into a request for the specific type T. Since those requests aren't handled by the SeededFactory<T>, it returns a NoSpecimen that ultimately results in an ObjectCreationException.

Possible Solution

I found that modifying the SeededFactory<T> class to relay also non-seeded requests for T to the custom factory function passing the default value of T as a seed solves the problem:

+ if (request != null && request.Equals(typeof(T))
+ {
+     return this.create(default(T));
+ }

 var seededRequest = request as SeededRequest;

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

I'm not sure whether this is the right answer, but at least it's a possibility. What do you guys think?

Compatibility

Regardless of how we decide to solve it, this is going to be a breaking change. As such, it should be done in the v4 branch.

ecampidoglio added a commit that referenced this issue Nov 12, 2015
The SeededFactory<T> class did not handle non-seeded requests
for the specified type T. This resulted in an ObjectCreationException
when AutoFixture had to create specimens to populate properties,
fields and parameters of type T.
@ploeh
Copy link
Member

ploeh commented Nov 12, 2015

I tried to reduce the reported problem to this:

[Fact]
public void FromSeedRepro()
{
    var fixture = new Fixture();
    fixture.Customize<Version>(x => x.FromSeed(s => s));
    var v1 = fixture.Create<Version>();
    var v2 = fixture.Create<Version>(new Version(2, 0));
    var ph = fixture.Create<PropertyHolder<Version>>();
}

The exception thrown looks like it's exactly the same exception as the one thrown by the code in http://stackoverflow.com/q/33619619/126014, for which I've recently submitted #470.

I wonder if #470 would also solve this issue...

ecampidoglio added a commit that referenced this issue Nov 13, 2015
@ecampidoglio
Copy link
Member

I run that test separately in #469 and #470. It fails in both of them but with different exceptions.

In #470 it fails with an ObjectCreationException while in #469 I got an InvalidOperationException:

System.InvalidOperationException : The specimen returned by the decorated ISpecimenBuilder is not compatible with System.Version.

The reason why it fails in #469 is that the factory function s => s returns null when AutoFixture creates a value for the property PropertyHolder<Version>.Property. In #470 the factory function is never invoked.

However, the repro for this particular issue is slightly different:

[Fact]
public void CustomizeFromSeedWillPopulatePropertyOfSameType()
{
    // Fixture setup
    var fixture = new Fixture();
    var seed = new Version();
    // Exercise system
    fixture.Customize<Version>(c => c.FromSeed(s => seed));
    // Verify outcome
    Assert.Equal(seed, fixture.Create<PropertyHolder<Version>>().Property);
}

In this case the factory function s => new Version() always returns a fixed seed value.

When I run it in #470 it fails with the same ObjectCreationException because the factory function is never invoked. In #469 it passes.

Now, this is where things get interesting. If I merge both #469 and #470 then both tests pass.

I added them in 378991f for reference.

ecampidoglio added a commit that referenced this issue Nov 16, 2015
The SeededFactory<T> class did not handle non-seeded requests
for the specified type T. This resulted in an ObjectCreationException
when AutoFixture had to create specimens to populate properties,
fields and parameters of type T.
ecampidoglio added a commit that referenced this issue Nov 16, 2015
ploeh added a commit that referenced this issue Nov 20, 2015
in order to indicate that support for unseed values when Fixture is
customised with FromSeed is a bug fix that resolves #467
(#467).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants