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

Allow registration of customizer that is invoked after an anonymous instance as been created #96

Closed
dhilgarth opened this issue Apr 24, 2013 · 22 comments

Comments

@dhilgarth
Copy link
Contributor

In the context of the auto mocking feature, I currently am trying to achieve the following:

After AutoFixture created the anonymous instance using NSubstitute I want to automatically configure the substitute.

Example:

fixture.RegisterConfigurator<IFoo>(x => x.Bar().Returns(42));

Is this already possible? If not, is this something you would find valuable, too?

@moodmosaic
Copy link
Member

Perhaps an approach similar to the one described in this post could work..

@ploeh
Copy link
Member

ploeh commented Apr 24, 2013

Can you solve it by applying the [Frozen] attribute?

@dhilgarth
Copy link
Contributor Author

@ploeh I don't see how. In fact, that attribute already is applied to the parameter of my unit test.
@moodmosaic That looks interesting, thanks. Would I be able to use that also to customize the creation itself, i.e. tell AutoFixture to never set a certain property when creating an instance of a certain class?

@dhilgarth
Copy link
Contributor Author

To answer my question about telling AutoFixture to never set a certain property when creating an instance of a certain class: That's already possible:

fixture.Customize<Bar>(c => c.Without(x => x.Fubar));

@ploeh
Copy link
Member

ploeh commented Apr 24, 2013

OK. Then I don't understand what the question is about... From your OP it looks like you want to configure the Test Double.

@dhilgarth
Copy link
Contributor Author

@ploeh I don't want to configure a specific test double. That would mean, I would have to do it again in every unit test.
I want to tell AutoFixture to do that configuration automatically for all instances of that type. I think the link @moodmosaic posted would allow me to achieve what I want somehow by using a PostProcessor customization. But I am not sure how to actually do that in a general way. What builder would I have to decorate?

@moodmosaic
Copy link
Member

You may implement a custom ISpecimenBuilder for the purpose of IFoo and Bar. You can decorate that builder with a Postprocessor where you may also modify the created instance.

@dhilgarth
Copy link
Contributor Author

@moodmosaic Thanks for the suggestion but that's not really a solution, is it? That would mean that IFoo instances wouldn't be created by AutoFixtures configured creation mechanism. It would always be created by whatever builder is hard coded in that IFooSpecimenBuilder.

@moodmosaic
Copy link
Member

Wouldn't it be possible to use NSubstitute and setup the stub? (e.g. Moq has a method Get<T>..) Perhaps inside the Postprocessor you could retrieve the mocked instance and configure it?

@dhilgarth
Copy link
Contributor Author

@moodmosaic : As far as I know, there is no such possibility. But I don't think that's the correct way anyway. This wouldn't work as soon as the configuration I want to register is for a concrete type instead of an interface.

@dhilgarth
Copy link
Contributor Author

Isn't there some hook that simply receives the created object?

@bartelink
Copy link
Contributor

@dhilgarth The impl of DisposableTracker might be interesting wrt that (related: http://stackoverflow.com/a/11657881/11635 )

@ploeh
Copy link
Member

ploeh commented Apr 24, 2013

AutoFixture is nothing but hooks. IIRC you built the NSubstitute Auto-Mocking extensions yourself. That's where your 'hook' is :)

Put a Postprocessor or similar around the ISpecimenBuilder you have there.

@dhilgarth
Copy link
Contributor Author

@ploeh I have the feeling I am a bit misunderstood here. I don't want to put a Postprocessor around a specific, hardcoded builder as you do in your AutoMoqPropertiesCustomization. I want to post process the created instance no matter what builder created it

@bartelink Looking into it, thanks.

@dhilgarth
Copy link
Contributor Author

@bartelink I think I found what I need: A ISpecimenBuilderTransformation that I add to the Behaviors of IFixture. This behavior in turn creates my instance of a ISpecimenBuilder which performs the actual work.
Thanks for the hint!

@dhilgarth
Copy link
Contributor Author

OK, this is what I came up with:

Usage:

fixture.PostprocessorFor<IFoo>(x => x.Bar().Returns(42));

Implemented via the following:

public class PostprocessorBehavior : ISpecimenBuilderTransformation
{
    private readonly ISpecimenCommand _command;
    private readonly IRequestSpecification _specification;

    public PostprocessorBehavior(ISpecimenCommand command, IRequestSpecification specification)
    {
        if (command == null)
            throw new ArgumentNullException("command");
        if (specification == null)
            throw new ArgumentNullException("specification");

        _command = command;
        _specification = specification;
    }

    public ISpecimenBuilder Transform(ISpecimenBuilder builder)
    {
        return new Postprocessor(builder, _command, _specification);
    }
}

public static class AutoFixtureExtensions
{
    public static void PostprocessorFor<T>(this IFixture fixture, Action<T> postProcessor)
    {
        fixture.Behaviors.Add(
            new PostprocessorBehavior(
                new ActionSpecimenCommand<T>(postProcessor), new ExactTypeSpecification(typeof(T))));
    }
}

Looks very nice to me.
@ploeh AutoFixture is nothing but hooks, alright. The problem is: Find the right one! ;-)
I think I finally found it.

Any comments?

@moodmosaic
Copy link
Member

👍
Looks good here :)

@bartelink
Copy link
Contributor

@dhilgarth Nice! In my use case, I was using 'typeof.IsAssignableFrom' as my type matching specification so nice to let that bubble up - I could use PostprocessorBehavior as-is though I'd use a different extension method.

As it happens, I only used it once and that case (AutoMoqing ASP.NET MVC IController) is better handled by the @moodmosaic trickery here: http://stackoverflow.com/a/14989866/11635 so I personally would let the dust settle before making this an actual Issue for packaged into Core

I've added a backlink to the cited CodePlex discussion pointing here.

@dhilgarth
Copy link
Contributor Author

It just occurred to me:
Isn't the following standard AutoFixture code doing exactly the same as the one I showed in the original question?

fixture.Customize<IFoo>(c => c.Do(x => x.Bar().Returns(42)));

@ploeh
Copy link
Member

ploeh commented Jul 4, 2013

I looks very similar - I think it'd do the same :)

@dhilgarth
Copy link
Contributor Author

Just wondering why nobody brought this up in this thread :-)

Anyway, I performed some tests. The semantics are a bit different.

fixture.Customize<Foo>(c => c.Do(x => x.Property = 9));

Please disregard that I could use c.With(x => x.Property, 9)
One would expect that all Do specifications are executed after the object has been fully constructed. That is not the case. The Foo instances created by a fixture customized with this won't have 9 as the value of Property. Instead, the value will be an anonymous int.
To achieve the expected effect, one would have to use fixture.Customize<Foo>(c => c.Without(x => x.Property).Do(x => x.Property = 9));.
This would be equivalent to fixture.PostProcessorFor<Foo>(x => x.Property = 9);

I wrote this mainly for documentation purposes, in case someone else sees the last two comments about Do.

However, I wonder if it is intended that Do gets executed before the auto-property behavior.

@ploeh
Copy link
Member

ploeh commented Jul 4, 2013

Thanks for the write-up; that all sounds correct to me.

Off the top of my head, I don't think there's any particular reason that the Do part is executed before auto-properties are applied.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants