没有无参数构造函数的.NET单元测试,以便于依赖注入

时间:2010-12-07 05:09:34

标签: unit-testing dependency-injection nunit inversion-of-control autofac

我正在尝试让单元测试不依赖于调用 container.Resolve< T>()来获取它们的依赖项。

我目前正在使用 AutoFac 2.2.4,并尝试 xUnit.NET NUnit ,但两者都有此问题

  

没有为此对象定义无参数构造函数

如何解决此问题?它是一个特定的单元测试框架,它将支持这个,或者只是如何配置所述框架?

我不应该这样做吗?或者我可以设置测试类来使用只有依赖的构造函数吗?

以下是一些代码:

public class ProductTests : BaseTest
{
    readonly private IProductRepository _repo;

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

    //working unit tests here with default constructor
} 

我是否选择在基类构造函数中错误地初始化容器?

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

2 个答案:

答案 0 :(得分:11)

最初的问题确实是由于测试框架的设计方式。它们都需要无参数构造函数才能实例化测试实例。理所当然。使用这些框架,不依赖构造函数进行测试初始化​​。这就是SetUp方法的目的。总而言之,测试类本身不适合注射。

IMO,当您开发不依赖容器的测试时,这就成了一个问题。毕竟,每个测试类应该关注一个“被测系统”(SUT)。为什么不让setup方法直接实例化该系统并提供每个依赖(通常以伪造的形式)?通过这种方式,您可以有效地从测试中删除另一个不必要的依赖项,即IoC框架。

旁注:我在测试中唯一涉及IoC框架的是“容器测试”。这些测试的重点是验证在使用application or assembly modules初始化容器后,可以从容器中解析某些服务。

答案 1 :(得分:4)

我只是允许我的测试依赖于Autofac,尽管我将其封装起来。我的所有TestFixtures都继承自Fixture,其定义如下:

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