11

J'essaie de faire en sorte que les tests unitaires ne reposent pas sur l'appel de container.Resolve < T () pour leurs dépendances.Un test d'unité .NET sans constructeur sans paramètre, pour faciliter l'injection de dépendances

J'utilise actuellement AutoFac 2.2.4, et essayé xUnit.NET et NUnit, mais les deux ont cette question:

Aucun constructeur défini parameterless pour cet objet

Comment puis-je résoudre ce problème? S'agit-il d'un cadre de test unitaire particulier qui le supportera, ou simplement de la configuration de ce cadre?

Est-ce que je ne devrais pas faire cela? Ou puis-je configurer la classe de test pour travailler avec le constructeur qui a sa seule dépendance?

est ici une partie du code:

public class ProductTests : BaseTest 
{ 
    readonly private IProductRepository _repo; 

    public ProductTests(IProductRepository r) 
    { 
     _repo = r; 
    } 

    //working unit tests here with default constructor 
} 

Ai-je choisi pour initialiser le conteneur à tort dans le constructeur de la classe de base?

public abstract class BaseTest 
{ 
    protected BaseTest() 
    { 
     var builder = new ContainerBuilder(); 
     builder.RegisterType<ProductRepository>().As<IProductRepository>(); 
     builder.Build(); 
    } 
} 
+1

Pourquoi la classe de test a-t-elle besoin d'un constructeur? Mettez le "injection" dans la méthode de configuration. –

Répondre

11

Le problème initial est en effet dû à la façon dont les frameworks de test sont conçus. Ils nécessitent tous un constructeur sans paramètre afin d'instancier les instances de test. Et à juste titre. Avec ces frameworks, le constructeur ne doit pas être utilisé pour l'initialisation du test. C'est le but de la méthode SetUp. Dans l'ensemble, les classes de test elles-mêmes ne sont pas adaptées à l'injection.

Et IMO, cela devient un non-problème lorsque vous développez vos tests pour ne pas dépendre du conteneur. Après tout, chaque classe de test devrait se concentrer sur un «système sous test» (SUT). Pourquoi la méthode d'installation instancie-t-elle directement ce système et fournit-elle chaque dépendance (généralement sous la forme de faux)? En faisant cela, vous avez effectivement supprimé une autre dépendance inutile de vos tests, à savoir le cadre IoC. Sur une note de côté: la seule fois où j'implique le framework IoC dans mes tests est dans mes "tests de conteneur". Ces tests se concentrent sur la vérification que certains services peuvent être résolus à partir du conteneur après l'initialisation du conteneur avec application or assembly modules.

+0

+1. Je suis complètement d'accord avec Peter. N'utilisez pas le conteneur dans vos méthodes de test, mais créez votre SUT manuellement (ou en utilisant un cadre de simulation). – Steven

+1

+1 également - si un composant a trop de dépendances pour être créé confortablement à la main dans un banc d'essai, il a trop de responsabilités. –

+0

remercie Peter, Steven et Nicholas d'avoir mis le raisonnement derrière pourquoi cette approche n'est pas nécessaire. Je suis d'accord que cette approche pourrait soutenir la création de tests qui pourraient facilement devenir incontrôlables et dépasser leurs limites «unitaires». –

4

Je permets simplement à mes tests d'avoir une dépendance sur Autofac, bien que je l'encapsule. Tous mes TestFixtures héritent de Fixture, qui est défini comme tel:

public class Fixture 
{ 
    private static readonly IContainer MainContainer = Ioc.Build(); 
    private readonly TestLifetime _testLifetime = new TestLifetime(MainContainer); 

    [SetUp] 
    public void SetUp() 
    { 
     _testLifetime.SetUp(); 
    } 

    [TearDown] 
    public void TearDown() 
    { 
     _testLifetime.TearDown(); 
    } 

    protected TService Resolve<TService>() 
    { 
     return _testLifetime.Resolve<TService>(); 
    } 

    protected void Override(Action<ContainerBuilder> configurationAction) 
    { 
     _testLifetime.Override(configurationAction); 
    } 
} 

public class TestLifetime 
{ 
    private readonly IContainer _mainContainer; 

    private bool _canOverride; 
    private ILifetimeScope _testScope; 

    public TestLifetime(IContainer mainContainer) 
    { 
     _mainContainer = mainContainer; 
    } 

    public void SetUp() 
    { 
     _testScope = _mainContainer.BeginLifetimeScope(); 
     _canOverride = true; 
    } 

    public void TearDown() 
    { 
     _testScope.Dispose(); 
     _testScope = null; 
    } 

    public TService Resolve<TService>() 
    { 
     _canOverride = false; 
     return _testScope.Resolve<TService>(); 
    } 

    public void Override(Action<ContainerBuilder> configurationAction) 
    { 
     _testScope.Dispose(); 

     if (!_canOverride) 
      throw new InvalidOperationException("Override can only be called once per test and must be before any calls to Resolve."); 

     _canOverride = false; 
     _testScope = _mainContainer.BeginLifetimeScope(configurationAction); 
    } 
}