Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I add common postprocessing applied after customization

Tags:

c#

autofixture

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.

like image 621
joozek Avatar asked Feb 04 '14 22:02

joozek


3 Answers

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.

like image 20
MarcinS Avatar answered Nov 02 '22 11:11

MarcinS


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);
    }
}
like image 178
Nikos Baxevanis Avatar answered Nov 02 '22 13:11

Nikos Baxevanis


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

like image 31
joozek Avatar answered Nov 02 '22 11:11

joozek