3

I'm trying to generate some subclasses of ApiController (WebAPI 2) using AutoFixture (3.50.6).

I customized AF to allow generating ApiControllers using this customization.

Because of further customization needs, I'd like to create a SpecimenBuilder that would create any type of ApiController and apply this configuration with a simple

fixture.Create<DummyController>();

I tried this test (NUnit 3) :

[TestFixture]
public class ApiControllerSpecimenBuilderTests
{
    [Test]
    public void ShouldCreateAControllerUsingSpecimenBuilder()
    {
        var fixture = new Fixture()
            .Customize(new AutoMoqCustomization())
            .Customize(new ApiControllerCustomization());
        fixture.Customizations.Add(new ApiControllerSpecimenBuilder());

        var ctl = fixture.Create<DummyController>();
    }
}

public class ApiControllerCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Inject(new UriScheme("http"));
        fixture.Customize<HttpConfiguration>(c => c
            .OmitAutoProperties());
        fixture.Customize<HttpRequestMessage>(c => c
            .Do(x =>
                x.Properties.Add(
                    HttpPropertyKeys.HttpConfigurationKey,
                    fixture.Create<HttpConfiguration>())));
        fixture.Customize<HttpRequestContext>(c => c
            .Without(x => x.ClientCertificate));
    }
}

public class ApiControllerSpecimenBuilder : ISpecimenBuilder
{
    public object Create(object request, ISpecimenContext context)
    {
        var t = request as Type;
        if (t == null || !typeof(ApiController).IsAssignableFrom(t))
        {
            return new NoSpecimen();
        }

        var controller = context.Resolve(t) as ApiController;

        // ...

        return controller;
    }
}

public class DummyController : ApiController
{

}

That fails with the following error :

Ploeh.AutoFixture.ObjectCreationException : AutoFixture was unable to create an instance of type System.RuntimeType because the traversed object graph contains a circular reference. [...]

Path: Foo.Common.Tests.AutoFixture.SpecimenBuilders.DummyController --> Foo.Common.Tests.AutoFixture.SpecimenBuilders.DummyController

Why does the DummyController have a reference to its own type ?

Moreover, if I change the test with an empty customization for DummyController, it passes :

[Test]
public void ShouldCreateAControllerUsingSpecimenBuilder()
{
    var fixture = new Fixture()
        .Customize(new AutoMoqCustomization())
        .Customize(new ApiControllerCustomization())
        .Customize(new DummyControllerCustomization()); // new customization
    fixture.Customizations.Add(new ApiControllerSpecimenBuilder());

    var ctl = fixture.Create<DummyController>();
}

public class DummyControllerCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customize<DummyController>(c => c);
    }
}

In this case, the SpecimenBuilder seems to not be hit anymore with the DummyController type. What does this empty customization do that makes the test pass ? Does it override the specimen builder ? But then why doesn't it throw the same exception, as I don't tell him to omit anything (and anyway, I wouldn't know what to make it omit...) ?

I guess I could use the OmitOnRecursionBehavior, but I'd like to keep the default behavior to avoid recursions in all the other cases, plus I'd rather understand what is happening (or if I did smth really stupid).

xlecoustillier
  • 16,183
  • 14
  • 60
  • 85

1 Answers1

1

Just remove ApiControllerSpecimenBuilder:

[TestFixture]
public class ApiControllerSpecimenBuilderTests
{
    [Test]
    public void ShouldCreateAControllerUsingSpecimenBuilder()
    {
        var fixture = new Fixture()
            .Customize(new AutoMoqCustomization())
            .Customize(new ApiControllerCustomization());
        //fixture.Customizations.Add(new ApiControllerSpecimenBuilder());

        var ctl = fixture.Create<DummyController>();
    }
}

The above version of your test passes (on my machine).

The problem is that ApiControllerSpecimenBuilder enters into an infinite recursion if it passes the initial Guard Clause:

var controller = context.Resolve(t) as ApiController;

The call to context.Resolve(t) enters a new object creation 'session'. AutoFixture asks each ISpecimenBuilder in its tree whether they can handle a request for t. When it reaches ApiControllerSpecimenBuilder, it responds by calling context.Resolve(t) again, and so on ad infinitum.

You don't need to do any of that work yourself, as AutoFixture is already perfectly capable of creating ApiController instances for you (as long as the ApiControllerCustomization is in place).

If I understand the overall use case correctly, however, the actual requirement is that you'd like to do some sort of post-processing on the ApiController instances, after AutoFixture has created the object for you.

The general solution to such a scenario is to use Postprocessor or Postprocessor<T>, but that can sometimes be a bit involved to get right. Often, there are simpler ways to achieve what you want to do.

If you need help with that, please ask another question. You don't need to put up a bounty next time, as I normally monitor the autofixture tag. This question somehow escaped my attention; sorry about that!

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • Thanks for your answer (no worries for the bounty). At least I understand better now how specimen builders work. I'll let you know if I'm running into further trouble :) – xlecoustillier Oct 05 '17 at 06:54
  • One question remains though : why is the specimen builder skipped when there is a customization in place ? – xlecoustillier Oct 05 '17 at 06:56
  • @X.L.Ant Because everything you do in AutoFixture gets packaged into an `ICustomization`, and [the order of Customizations matter](http://blog.ploeh.dk/2012/07/31/TheorderofAutoFixtureCustomizationsmatter). So in your particular example, the second customization overrides the first one, and since it doesn't do anything, it's equivalent to my solution of entirely removing `ApiControllerSpecimenBuilder`. – Mark Seemann Oct 05 '17 at 07:11
  • @X.L.Ant BTW, the linked article claims AutoFixture can't create instances of `IEnumerable`. That was true when the article was written, but isn't true today. The rest of the article still fits current behaviour, AFAICT. – Mark Seemann Oct 05 '17 at 07:13