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


我试图让单元测试不依赖于调用 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();
    }
}

12921
2017-12-07 05:09


起源

为什么测试类需要有构造函数?将“注入”放入设置方法中。 - Adam Vandenberg


答案:


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

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

旁注:我在测试中唯一涉及IoC框架的是“容器测试”。这些测试的重点是验证在初始化容器后可以从容器中解析某些服务 应用程序或组装模块


11
2017-12-07 11:45



+1。我完全赞同彼得。不要在测试方法中使用容器,而是手动创建SUT(或使用模拟框架)。 - Steven
+1也 - 如果一个组件有太多的依赖关系,无法在测试夹具中手工创建,它有太多的责任。 - Nicholas Blumhardt
感谢彼得,史蒂文和尼古拉斯提出的理由,说明为什么这种方法没有必要。我同意这种方法可以支持创建容易失控并超出其“单位”范围的测试。 - Nick Josevski
只需补充一点,测试框架可用于单元测试之外,用于更高级别测试的自动化。在这种情况下,使用DI容器可以用于匹配生产依赖性并简化SUT接线。 - Marc Climent


我只是允许我的测试依赖于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);
    }
}

4
2017-12-13 14:52