How can I add common postprocessing applied after customization
Asked Answered
L

3

10

I have defined ISpecimenBuilder for my models and use it like that:

new Fixture().Customize(new ModelCustomization());

I want to use it in most of my tests concerning model. I also want to apply some form of post-processing in one of my test classes. Specifically I want to fill property CompanyHistory of all created Offers. It feels like it could be done like that:

fixture.Build<Offer>()
.With(o => o.CompanyHistory, _previouslyCreatedCompanyHistory)
.Create();

But Build<T> disables all customizations and I need them.

Can I do something like that?

fixture.Build<Offer>()
.WithCustomization(new ModelCustomization()) // there is no such method, but i'd like it to be
.With(o => o.CompanyHistory, _previouslyCreatedCompanyHistory)
.Create();

Or should I write my own Behavior? If so, can someone provide me with guidelines on doing that?

EDIT: I feel I have to stress out that I want to use both my common customization (ModelCustomization) and Postprocessor

EDIT 2: What I meant from the beginning is that ModelCustomization can (and should) create Offer and my to-be postprocessor should use that already created specimen and fill some of its properties.

Loath answered 4/2, 2014 at 22:28 Comment(0)
L
1

I ended up writing following Customization:

private class OfferWithCompanyModelCustomization: ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customizations.Add(new FilteringSpecimenBuilder(new Postprocessor(
            new ModelSpecimenBuilder(), new FillModelPropertiesCommand()), new ExactTypeSpecification(typeof(Offer))));
    }

    private class FillModelPropertiesCommand : ISpecimenCommand
    {
        public void Execute(object specimen, ISpecimenContext context)
        {
            var offer = specimen as Offer;
            offer.CompanyHistory = (CompanyHistory)context.Resolve(typeof(CompanyHistory));
        }
    }
}

This works, but it's far from perfect. As you can see, I refer to ModelSpecimenBuilder directly, so I'm dependent on implementation (as postprocessor I'd like not to be).

Answer posted by @Nikos is not satisfying, because his customization ignores previous customizations in chain of responsibility.

When we invoke the Create method, a CompositeSpecimenBuilder will invoke the Create method of all its contained builders until one of them provides a specimen. At this point the request is considered to be satisfied, and the rest of the builders are ignored.

source: AutoFixture Documentation

Loath answered 5/2, 2014 at 18:23 Comment(0)
L
5

Here is how you can create and use a Postprocessor in this case:

[Fact]
public void Test()
{
    var fixture = new Fixture();

    // (You may also include other customizations here.)

    fixture.Customizations.Add(
        new FilteringSpecimenBuilder(
            new Postprocessor(
                new MethodInvoker(
                    new ModestConstructorQuery()),
                new OfferFiller()),
            new OfferSpecification()));

    var offer = fixture.Create<Offer>();
    // -> offer.CompanyHistory has the value supplied in OfferFiller command.
}

The OfferFiller command is defined as:

internal class OfferFiller : ISpecimenCommand
{
    public void Execute(object specimen, ISpecimenContext context)
    {
        if (specimen == null)
            throw new ArgumentNullException("specimen");
        if (context == null)
            throw new ArgumentNullException("context");

        var offer = specimen as Offer;
        if (offer == null)
            throw new ArgumentException(
                "The specimen must be an instance of Offer.",
                "specimen");

        Array.ForEach(offer.GetType().GetProperties(), x =>
        {
            if (x.Name == "CompanyHistory ")
                x.SetValue(offer, /*value*/);
            else 
                x.SetValue(offer, context.Resolve(x.PropertyType));
        });
    }
}

The OfferSpecification is defined as:

internal class OfferSpecification : IRequestSpecification
{
    public bool IsSatisfiedBy(object request)
    {
        var requestType = request as Type;
        if (requestType == null)
            return false;

        return typeof(Offer).IsAssignableFrom(requestType);
    }
}
Landloper answered 5/2, 2014 at 5:33 Comment(5)
Thanks for answer! Problem is I want to use both my common customization (ModelCustomization, used in all tests) and my less common postprocessor (OfferFiller, used in few tests) at the same time (check my 3rd code box). I figured that I can create FilteringSpecimenBuilder with original ModelSpecimenBuilder and custom OfferFiller as Command, but I feel It's not robust enoughLoath
You can do that as well; include any customizations after creating a new Fixture instance (see the updated code comment).Landloper
yeah, but I think that only one customization gets to create the specimen, and I want it created by ModelCustomization and later post-processedLoath
The order of AutoFixture Customizations matter but you can have as many as you like.Landloper
F# wrapper and example: gist.github.com/bartelink/8824390 (Yes, PRs with invalidArg calls etc. welcome :P)Phantom
D
2

I had a similar problem and have tried solutions mentioned here, but they didn't work as expected. Finally, I've found an implementation of a PostProcessWhereIsACustomization class, that does exactly what I needed:

AutoFixture customization to allow insertion of arbitrary postprocessing logic a la Customize( c=>c.Do()) but in a global manner Revised for v3 (initally for v2)

May save somebody some Googling.

Decoteau answered 9/9, 2016 at 10:48 Comment(0)
L
1

I ended up writing following Customization:

private class OfferWithCompanyModelCustomization: ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customizations.Add(new FilteringSpecimenBuilder(new Postprocessor(
            new ModelSpecimenBuilder(), new FillModelPropertiesCommand()), new ExactTypeSpecification(typeof(Offer))));
    }

    private class FillModelPropertiesCommand : ISpecimenCommand
    {
        public void Execute(object specimen, ISpecimenContext context)
        {
            var offer = specimen as Offer;
            offer.CompanyHistory = (CompanyHistory)context.Resolve(typeof(CompanyHistory));
        }
    }
}

This works, but it's far from perfect. As you can see, I refer to ModelSpecimenBuilder directly, so I'm dependent on implementation (as postprocessor I'd like not to be).

Answer posted by @Nikos is not satisfying, because his customization ignores previous customizations in chain of responsibility.

When we invoke the Create method, a CompositeSpecimenBuilder will invoke the Create method of all its contained builders until one of them provides a specimen. At this point the request is considered to be satisfied, and the rest of the builders are ignored.

source: AutoFixture Documentation

Loath answered 5/2, 2014 at 18:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.