Idiomatic way to override a ctor arg

Oct 18, 2010 at 4:52 PM

if I have:

class A

{

public A( string x, B b);

}

and I want to fixture.CreateAnonymous<A>, how do I feed in my value for x?

i.e., something nicer than having to

var a = fixture.Build<A>(x=>new A("xvalue", fixture.CreateAnonymous<B>()).CreateAnonymous()

or

fixture.Customize<A>(x=>x.FromFactory(()=>new A("xvalue", fixture.CreateAnonymous<B>()));

var a = fixture.CreateAnonymous<A>;

Maybe something like:

var a = fixture.Build<A>().WithCtorArg("xvalue").CreateAnonymous() // infers as there's only one arg

or

var a = fixture.Build<A>().WithCtorArg("x","xvalue").CreateAnonymous() // infers as there's only one arg

? Or am I missing something (I didnt look too hard in the docs, sorry!)

Coordinator
Oct 19, 2010 at 9:15 PM

Thanks for asking. This is a good question that provides me with an opportunity to demonstrate how powerful and extensible AutoFixture is :)

Depending on your need, you have a couple of options. One that is perhaps only marginally better than the ones you suggest is something like this:

var m = fixture.Build<Money>()
    .FromFactory((decimal amount) => 
        new Money(amount, "DKK"))
    .CreateAnonymous();

It takes advantage of one of the several overloads of the FromFactory method that automatically supplies specimens for all the constructor arguments that you don't care about. This has the advantage of being type-safe, but this can also be a liability because it can constrain our ability to refactor the constructor in question. In any case, just to round off this option, you can move it to a customization so that it applies in general to the Fixture instance:

fixture.Customize<Money>(c => 
    c.FromFactory((decimal amount) => 
        new Money(amount, "DKK")));

However, you seem to request a more convention-based approach (which I can only applaud). In this case, my latest blog post should hopefully describe how to accomplish this. As you can see, at this point we leave AutoFixture's fluent API and go straight to the kernel. This makes a lot of sense to me as the fluent API was always meant to address mostly those scenarios where type inferencing helps us in constructing strongly typed expressions. As far as I'm aware, there's no better way to address a constructor in a strongly typed manner than shown above, but I normally find it counter-productive as it constrains refactoring.

Please let me know if this does not sufficiently answer your question.

Oct 22, 2010 at 3:25 PM

Superb - thanks for the excellent example, my first application of this is:

public static class FixtureExtensions
    {
        public static TypeConstructorsContext<T> CustomizeArgumentCompositionOnConstructorsOf<T>( this Fixture that )
        {
            return new TypeConstructorsContext<T>( that );
        }

        public class TypeConstructorsContext<T>
        {
            readonly Fixture _that;
            public TypeConstructorsContext( Fixture that )
            {
                _that = that;
            }

            public void Inject<TArg>( TArg value )
            {
                _that.Customizations.Add( new CtorArgInjector<T, TArg>( parameterinfo => parameterinfo.ParameterType == typeof( TArg ), value ) );
            }
        }

        class CtorArgInjector<T, TArg> : ISpecimenBuilder
        {
            Predicate<ParameterInfo> _filter;
            readonly TArg _value;
            public CtorArgInjector( Predicate<ParameterInfo> filter, TArg value )
            {
                _filter = filter;
                _value = value;
            }

            public object Create( object request, ISpecimenContext context )
            {
                var pi = request as ParameterInfo;
                if ( pi != null && pi.Member.DeclaringType == typeof( T ) && _filter( pi ) )
                    return _value;

                return new NoSpecimen( request );
            }
        }
    }
which allows me to express your second example as:

fixture.CustomizeArgumentCompositionOnConstructorsOf<Money>().Inject( "DKK");
I realize the style of implementation is not in keeping with the general AF fluent style. Over time, I'll generalize the above and will also spend some time in the AF codebase so I can express the above much more cleanly.

Coordinator
Oct 22, 2010 at 6:57 PM

Cool, thanks for sharing - looking forward to see where this goes :)