11

Estoy tratando de tener las pruebas de la unidad no se basan en llamar al contenedor .Resolver <T>() para sus dependencias.A .NET Unidad de prueba sin un constructor sin parámetros, para facilitar la inyección de dependencia

Actualmente estoy usando autofac 2.2.4, y trató xUnit.NET y NUnit, pero ambos tienen este problema:

Sin constructor sin parámetros definidos para este objeto

¿Cómo puedo superar este problema? ¿Es un marco de prueba unitario particular que lo soportará, o simplemente cómo se configura dicho marco?

¿No debería estar haciendo esto? ¿O puedo configurar la clase de prueba para trabajar con el constructor que tiene su única dependencia?

Aquí es una parte del código:

public class ProductTests : BaseTest 
{ 
    readonly private IProductRepository _repo; 

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

    //working unit tests here with default constructor 
} 

qué elegí para inicializar el contenedor erróneamente en el constructor de la clase base?

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

¿Por qué la clase de prueba necesita tener un constructor? Pon la "inyección" en el método de configuración. –

Respuesta

11

El problema inicial se debe a la forma en que se diseñaron los marcos de prueba. Todos requieren un constructor sin parámetros para instanciar instancias de prueba. Y con razón eso. Con estos marcos, no se debe confiar en el constructor para la inicialización de la prueba. Ese es el propósito del método SetUp. En general, las clases de prueba en sí no son aptas para inyección.

Y OMI, esto se convierte en un problema cuando desarrolla sus pruebas para no depender del contenedor. Después de todo, cada clase de prueba debe enfocarse en un "sistema bajo prueba" (SUT). ¿Por qué no hacer que el método de instalación cree una instancia de ese sistema directamente y proporcione cada dependencia (generalmente en forma de falsificaciones)? Al hacerlo de esta forma, ha eliminado efectivamente otra dependencia innecesaria de sus pruebas, concretamente el marco de trabajo de IoC.

En una nota al margen: la única vez que involucro el marco de trabajo de IoC en mis pruebas es en mis "pruebas de contenedor". Estas pruebas se centran en verificar que ciertos servicios se pueden resolver desde el contenedor después de que el contenedor se haya inicializado con application or assembly modules.

+0

+1. Estoy completamente de acuerdo con Peter. No use el contenedor en sus métodos de prueba, pero cree su SUT manualmente (o utilizando un marco de burla). – Steven

+1

+1 también: si un componente tiene demasiadas dependencias para crear cómodamente a mano en un dispositivo de prueba, tiene demasiadas responsabilidades. –

+0

gracias Peter, Steven y Nicholas por poner el razonamiento detrás de por qué ese enfoque no es necesario. Estoy de acuerdo en que este enfoque podría apoyar la creación de pruebas que fácilmente podrían salirse de control y exceder sus límites de "unidad". –

4

Solo permito que mis pruebas dependan de Autofac, aunque lo encapsulo. Todos mis TestFixtures heredan de Fixture, que se define como tal:

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); 
    } 
} 
Cuestiones relacionadas