2017-09-27 5 views
3

J'essaie de générer des sous-classes d'ApiController (WebAPI 2) en utilisant AutoFixture (3.50.6).Pourquoi ai-je une référence circulaire DummyApiController -> DummyApiController lorsque j'utilise un constructeur d'échantillons?

J'ai AF personnalisé pour permettre de générer ApiControllers en utilisant this customization.

En raison des besoins supplémentaires de personnalisation, je voudrais créer un SpecimenBuilder qui créerait tout type de ApiController et d'appliquer cette configuration avec un simple

fixture.Create<DummyController>(); 

J'ai essayé ce 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 
{ 

} 

qui échoue avec l'erreur suivante:

Ploeh.AutoFixture.Object CreationException: AutoFixture n'a pas pu créer une instance de type System.RuntimeType, car le graphique objet traversé contient une référence circulaire. [...]

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

Pourquoi le DummyController ont une référence à son propre type?

De plus, si je change le test avec une personnalisation vide pour DummyController, il passe:

[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); 
    } 
} 

Dans ce cas, le SpecimenBuilder semble ne pas être plus touché avec le type DummyController. Que fait cette personnalisation vide qui fait passer le test? Surclasse-t-il le constructeur de spécimens? Mais alors pourquoi ne lance-t-il pas la même exception, puisque je ne lui dis pas d'omettre quoi que ce soit (et de toute façon, je ne saurais pas quoi faire pour l'omettre ...)? Je suppose que je pourrais utiliser le OmitOnRecursionBehavior, mais je voudrais garder le comportement par défaut pour éviter les récursions dans tous les autres cas, et je comprendrais plutôt ce qui se passe (ou si j'ai vraiment mal fait).

+0

Question parfaite: j'ai eu un repro en une tentative. Bon travail :) –

Répondre

1

Tout supprimer 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>(); 
    } 
} 

La version ci-dessus de vos passes de test (sur ma machine).

Le problème est que ApiControllerSpecimenBuilder entre dans une récursion infinie si elle passe la clause de sauvegarde initiale:

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

L'appel à context.Resolve(t) entre dans une nouvelle création d'objet « session ». AutoFixture demande à chaque ISpecimenBuilder dans son arbre s'il peut gérer une demande pour t. Quand il atteint ApiControllerSpecimenBuilder, il répond en appelant à nouveau context.Resolve(t), et ainsi de suite ad infinitum.

Vous n'avez pas besoin de faire ce travail vous-même, car AutoFixture est déjà parfaitement capable de créer des instances ApiController pour vous (tant que le ApiControllerCustomization est en place).Si je comprends correctement le cas d'utilisation global, l'exigence réelle est que vous souhaitiez effectuer un post-traitement sur les instances ApiController, après que AutoFixture ait créé l'objet pour vous.

La solution générale à un tel scénario est d'utiliser Postprocessor ou Postprocessor<T>, mais cela peut parfois être un peu compliqué pour obtenir le bon résultat. Souvent, il existe des moyens plus simples de réaliser ce que vous voulez faire.

Si vous avez besoin d'aide, posez une autre question. Vous n'avez pas besoin de mettre une prime la prochaine fois, car je surveille normalement le tag autofixture. Cette question a échappé à mon attention; Désolé pour ça!

+0

Merci pour votre réponse (pas de soucis pour la prime). Au moins, je comprends mieux maintenant comment fonctionnent les spécimens. Je vous ferai savoir si je suis confronté à d'autres problèmes :) –

+0

Une question subsiste cependant: pourquoi le constructeur d'échantillons est-il sauté quand il y a une personnalisation en place? –

+0

@ X.L.Ant Parce que tout ce que vous faites dans AutoFixture est emballé dans un 'ICustomization', et [l'ordre des personnalisations est important] (http://blog.ploeh.dk/2012/07/31/TheorderofAutoFixtureCustomizationsmatter). Donc, dans votre exemple particulier, la deuxième personnalisation remplace la première, et comme elle ne fait rien, elle équivaut à ma solution consistant à supprimer complètement 'ApiControllerSpecimenBuilder'. –