CreateAnonymous doesn't seem to care about Register-ed instances

Mar 15, 2010 at 2:36 PM
Edited Mar 15, 2010 at 2:45 PM

Hi,

I'm probably missing something, but why doesn't the 2nd test below work:

 

   
    class FooPart
    {
    }

    class Foo
    {
        public IEnumerable<FooPart> Parts {get; set; }    
    }


    public class FooTests
    {
        #region Fixture
        private Fixture fixture = new Fixture();
        #endregion

        #region Tests

        [Fact]
        public void CanCreateWithDirectRegistration()
        {
            // Setup
            var originalFoo = fixture.Build<Foo>().Without(x => x.Parts).CreateAnonymous();
            fixture.Register(originalFoo);

            // Exercise system
            var resolvedFoo = fixture.CreateAnonymous<Foo>();

            // Verify
            Assert.Same(resolvedFoo, originalFoo);

            // TearDown
        }

        [Fact]
        public void CanCreateWithFuncRegistration()
        {
            // Setup
            var originalFoo = fixture.Build<Foo>().Without(x => x.Parts).CreateAnonymous();
            fixture.Register(() => originalFoo);

            // Exercise system
            var resolvedFoo = fixture.CreateAnonymous<Foo>();

            // Verify
            Assert.Same(resolvedFoo, originalFoo);

            // TearDown
        }
        #endregion
    }

 

So it seems there's something wrong with registering an instance using the Func approach. OR I'm doing something wrong :)

 

Coordinator
Mar 15, 2010 at 2:59 PM

What happens in the second test?

I'm currently looking at a repro in MSTest. In that repro, the second test throws an exception that I can definitely explain, but before I launch into a detailed explanation, I'd like to know if my repro is correct or you are experiencing some other symptom.

Mar 15, 2010 at 3:03 PM

For me it also throws an exception: "Ploeh.AutoFixture.ObjectCreationException: AutoFixture was unable to create an instance of type System.Collections.Generic.IEnumerable`1[AutoFixtureRepro.FooPart], since it has no public constructor."

It looks like it's trying to create an instance of Foo from scratch, ignoring the registered Foo instance (originalFoo ;)).

Coordinator
Mar 15, 2010 at 3:21 PM

Thanks, that's the same exception I'm seeing.

Well, you've certainly stumbled upon one of the less intuitive areas of AutoFixture. The thing is that the Register method is one of the earliest methods of Fixture, and the semantics surrouding it are a bit vague. What it actually does is that it lets you specify a function that constructs the instance. The rest of the builder pipeline still runs after the instance has been constructed, which means that AutoProperties are still in play.

In this case it means that although is does use originalFoo, it subsequently attempts to assign values to all writable properties. When it reaches the Parts property, it doesn't know how to create an instance of IEnumerable<FooPart> because it's an interface.

I admit that the Register method isn't particularly intuitive, and I have several times considered changing the behavior, but since it would be a breaking change, I've been shying away from it so far. Perhaps I should reconsider it once again. It even occasionally trips me up :)

Until then, you have some options:

First of all, according to the Framework Design Guidelines, collection properties should be read-only. Making the Parts property read-only would solve this particular issue, but not address the underlying issue.

As a more generalized solution, you can use the Customize method like this: 

var originalFoo = fixture.Build<Foo>().Without(x => x.Parts).CreateAnonymous();
fixture.Customize<Foo>(ob => ob.WithConstructor(() => originalFoo).OmitAutoProperties());

But here's a more succinct alternative:

var originalFoo = fixture.Freeze<Foo>(ob => ob.Without(x => x.Parts));

The Freeze method is essentally a shortcut method around the CreateAnonymous/Register coding idiom.

Mar 15, 2010 at 3:31 PM

Thanks for the answer, it's clear now. :)