如何在单元测试中调用依赖注入类的方法?

7

我是新手,对单元测试和依赖注入不太熟悉,在使用依赖注入的类中调用一个方法时,找不到简单的方法。

这是我的类

public class AgentProvisioningServiceHelpher : IAgentProvisioningServiceHelpher
{
    private readonly IExcelParser _excelParser;
    private readonly SupervisorDbContext _SupervisorDbContext;
    private readonly SchedulerNoTrackingDbContext _SchedulerDbContext;

    // constructor
    public AgentProvisioningServiceHelpher(IExcelParser excelParser, SupervisorDbContext supervisorDbContext, SchedulerNoTrackingDbContext SchedulerDbContext)
    {
        _excelParser = excelParser;
        _SupervisorDbContext = supervisorDbContext;
        _SchedulerDbContext = SchedulerDbContext;
    }

    // Function that I want to call in unit test
    public int SimpleMethodToTest(int InputId) 
    {
        return InputId + 1;
    } 
}

这是我的接口代码

    public interface IAgentProvisioningServiceHelpher
    {
        int SimpleMethodToTest(int InputId);
    }

这是我的单元测试代码,我正在使用 Xunit。

public class UnitTest1
{
    private IAgentProvisioningServiceHelpher _sut; 
    private IExcelParser _excelParser;
    private SupervisorDbContext _DBcontext1;
    private SchedulerNoTrackingDbContext _DBcontext2;

    public UnitTest1(IExcelParser excelParser, SupervisorDbContext DBcontext1, SchedulerNoTrackingDbContext DBcontext2, IAgentProvisioningServiceHelpher sut)
    {
        _excelParser = excelParser;
        _DBcontext1 = DBcontext1;
        _DBcontext2 = DBcontext2;
        _sut = sut;
    }

    [Fact]
    public void SimpleMethodToTest_Shall_ReturnPlus1()
    {
        // Arrange
        int Input_Int = 1;

        // Act
        // I try to tell the interface to map with the class I want to test
        IAgentProvisioningServiceHelpher _sut = new AgentProvisioningServiceHelpher(_excelParser, _DBcontext1, _DBcontext2);

        // Then I try to call the interface method 
        var result = _sut.SimpleMethodToTest(Input_Int);

        // Assert
        Assert.Equal(2, result);
    }
}

当我尝试运行测试时,Visual Studio 报告以下错误 - 我应该如何解决?

UnitTest1.cs 第33行

下面的构造函数参数没有匹配的装置数据:IExcelParser excelParser、SupervisorDbContext DBcontext1、SchedulerNoTrackingDbContext DBcontext2、IAgentProvisioningServiceHelpher sut


这个回答解决了您的问题吗?单元测试xunit,以下构造函数参数没有匹配的 Fixture 数据 - Rajeesh Menoth
Xunit不使用DI来解析引用,因此请删除构造函数参数并尝试创建模拟对象。 - Rajeesh Menoth
在V3中,有一个关于这种事情的开放功能请求,但它尚未完成,绝对不是针对V2的,它仅适用于https://xunit.net/docs/shared-context。 - Ruben Bartelink
2个回答

11
应用DI时,您将依赖项的创建推迟到“最后负责的时刻”。这意味着尽可能地将创建依赖项的负担推迟。但是,在应用程序的某个地方,这些依赖项需要被创建。
这个创建依赖关系的地方被称为组合根。对于正在运行的应用程序,组合根很可能是应用程序的Main方法,或者至少在应用程序的启动路径附近。

单元测试中的组合

在编写单元测试时,每个单元测试本身充当组合根。这意味着单元测试本身(或它调用的方法)负责组成它需要测试的类。这意味着组成不再被推迟,也不再被推上。这就是您想要做的:将对象组成的责任推给单元测试框架。
尽管从技术上讲,一些单元测试框架允许你拦截测试类的创建方式,让测试框架提供依赖关系往往没有太多意义,因为单元测试本身需要控制所创建的确切依赖关系。测试不仅知道依赖项的确切类型(通常是某种伪装实现),而且还需要配置这些(伪装)依赖项或查询它们的结果以断言测试的正确性。

这意味着,与其试图在UnitTest1的构造函数中注入AgentProvisioningServiceHelper的依赖项,不如让SimpleMethodToTest_Shall_ReturnPlus1方法来控制。例如:

[Fact]
public void SimpleMethodToTest_Shall_ReturnPlus1()
{
    // Arrange
    int input = 1;
    int expectedResult = 2;

    var sut = new AgentProvisioningServiceHelpher(
        new FakeExcelParser(),
        new FakeSupervisorDbContext(),
        new FakeSchedulerNoTrackingDbContext());

    // Act
    var actualResult = sut.SimpleMethodToTest(input);

    // Assert
    Assert.Equal(expectedResult, actualResult);
}

在每个单元测试中创建被测试类的所有依赖关系,随着测试数量的增加,变得越来越难以维护。在这种情况下,将此组合逻辑从测试中提取出来,放到帮助方法或帮助类中,是一个好的实践。关键在于,在这种情况下,确保测试仅提供特定测试所需的依赖项,而将其余部分留空。例如:
[Fact]
public void Parser_should_always_be_called()
{
    // Arrange
    var parser = new FakeExcelParser();

    AgentProvisioningServiceHelpher sut = this.CreateSut(excelParser: parser);

    // Act
    sut.SimpleMethodToTest(0);

    // Assert
    Assert.IsTrue(parser.GotCalled);
}

private AgentProvisioningServiceHelpher CreateSut(
    IExcelParser excelParser = null,
    SupervisorDbContext supervisorDbContext = null,
    SchedulerNoTrackingDbContext schedulerDbContext = null)
{
    return new AgentProvisioningServiceHelpher(
        excelParser ?? new FakeExcelParser(),
        supervisorDbContext ?? new FakeSupervisorDbContext(),
        schedulerDbContext ?? new FakeSchedulerNoTrackingDbContext());
}

在这个测试中,只提供了ExcelParser,因为在测试期间它被显式地查询。其他两个依赖项将由CreateSut方法提供默认(可能是虚假的)空实现。

在这种情况下,CreateSut成为组合根的一部分。

集成测试中的组合

在编写单元测试时,通常手动连接依赖项,如上所示。然而,在编写集成测试时,涉及到的对象数量通常更多,并且需要类似于在生产应用程序中组合的对象结构(有时替换一些依赖项)。让单个测试方法或测试类手动重新创建完整的对象结构通常会很繁琐和容易出错。应用程序对象结构的更改可能会影响到许多测试,并且很容易导致难以维护的系统。

相反,在集成测试期间,常见的做法是尝试重用运行应用程序的组合根使用的相同对象组合逻辑。当您使用DI容器来组合应用程序的对象图时,通常意味着重用这些相同的DI容器注册。

一个集成测试将重复使用相同的 DI 容器配置,模拟运行集成测试所需的一些依赖项,并解析受测类并调用其方法。但是,集成测试仍然不会从外部注入这些依赖项,因为它可能需要对创建的内容进行一定的控制。即使它将对象组合的一部分委托给单独的 Composer 类(DI 容器),但集成测试仍然是自己的组合根。以下是一个集成测试的示例:
[Fact]
public void Some_integration_test()
{
    // Arrange
    int input = 1;
    int expectedResult = 2;

    // Mock object
    var parser = new FakeExcelParser();

    // Create a valid container to resolve object graphs from
    var container = TestBootstrapper.BuildContainer();

    // Configure it especially for this test (note that I'm inventing a
    // DI Container API here. API will very per DI Container)
    container.Replace<IExcelParser>(parser);

    // Resolve the SUT from the DI Container
    var sut = container.Resolve<AgentProvisioningServiceHelpher>();

    // Act
    var actualResult = sut.SimpleMethodToTest(input);

    // Assert
    Assert.Equal(expectedResult, actualResult);
}

这个集成测试使用一个 TestBootstrapper 类,可能会在不同的集成测试中共享使用:
public static class TestBootstrapper
{
    public static Container BuildContainer()
    {
        // Request a fully configured DI Container instance from the
        // actual application. This ensures that the integration test
        // runs using the exact same object graphs as the final application.
        var container = RealApplication.Bootstrapper.BuildContainer();

        // Replace dependencies that should never be used during the
        // integration tests.
        container.Replace<IHardDiskFormatter, FakeDiskFormatter>();
        container.Replace<ISmsSender, FakeSmsSender>();
        container.Replace<IPaymentProvider, FakePaymentProvider>();

        return container;
    }
}

当然,这与单元测试非常不同,因为那里有很高的隔离水平。

0
你没有使用依赖注入容器,所以没有注入你的依赖项。
你需要自己实例化每个依赖项并将其传递给构造函数。
因为这个问题是为了测试使用IoC的类,我创建了BuddyInjector包来在你的测试中注入你的依赖项。
BuddyInjector是一个小而简单的依赖注入器。主要目的是准备依赖项,并在需要时简单地覆盖每个测试的依赖项。

请查看如何不成为垃圾邮件发送者 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接