如何为我的后台服务编写单元测试?

Xyl*_*uun 12 c# .net-core asp.net-core-hosted-services

我正在使用 .NET Core 中的 HostBuilder(不是 WebHost !)。

我在我的应用程序中运行了一个托管服务,它覆盖了后台服务的 ExecuteAsync/StopAsync 方法,我想对其进行单元测试。

这是我的托管服务:

public class DeviceToCloudMessageHostedService : BackgroundService
{
    private readonly IDeviceToCloudMessageService _deviceToCloudMessageService;
    private readonly AppConfig _appConfig;

    public DeviceToCloudMessageHostedService(IDeviceToCloudMessageService deviceToCloudMessageService, IOptionsMonitor<AppConfig> appConfig)
    {
        _deviceToCloudMessageService = deviceToCloudMessageService;
        _appConfig = appConfig.CurrentValue;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            await _deviceToCloudMessageService.DoStuff(stoppingToken);
            await Task.Delay(_appConfig.Parameter1, stoppingToken);
        }
    }
    
    public override Task StopAsync(CancellationToken cancellationToken)
    {
        Log.Information("Task Cancelled");
        _deviceToCloudMessageService.EndStuff();
        return base.StopAsync(cancellationToken);
    }
Run Code Online (Sandbox Code Playgroud)

我已经找到了这篇文章:.NET Core 中托管服务的集成测试

但它是为 QueuedBackgroundService 解释的,我真的不知道我是否可以用同样的方式测试我的。

我只想知道我的代码是否被执行。我不想要任何具体的结果。你知道我如何测试它吗?

Nko*_*osi 13

您仍然应该能够遵循与链接答案类似的格式。

模拟依赖项并注入它们,调用被测方法并断言预期的行为。

下面使用 Moq 来模拟依赖项以及ServiceCollection注入依赖项的繁重工作。

[TestMethod]
public async Task DeviceToCloudMessageHostedService_Should_DoStuff() {
    //Arrange
    IServiceCollection services = new ServiceCollection();
    services.AddSingleton<IHostedService, DeviceToCloudMessageHostedService>();
    //mock the dependencies for injection
    services.AddSingleton(Mock.Of<IDeviceToCloudMessageService>(_ =>
        _.DoStuff(It.IsAny<CancellationToken>()) == Task.CompletedTask
    ));
    services.AddSingleton(Mock.Of<IOptionsMonitor<AppConfig>>(_ =>
        _.CurrentValue == Mock.Of<AppConfig>(c => 
            c.Parameter1 == TimeSpan.FromMilliseconds(1000)
        )
    ));
    var serviceProvider = services.BuildServiceProvider();
    var hostedService = serviceProvider.GetService<IHostedService>();

    //Act
    await hostedService.StartAsync(CancellationToken.None);
    await Task.Delay(1000);//Give some time to invoke the methods under test
    await hostedService.StopAsync(CancellationToken.None);

    //Assert
    var deviceToCloudMessageService = serviceProvider
        .GetRequiredService<IDeviceToCloudMessageService>();
    //extracting mock to do verifications
    var mock = Mock.Get(deviceToCloudMessageService);
    //assert expected behavior
    mock.Verify(_ => _.DoStuff(It.IsAny<CancellationToken>()), Times.AtLeastOnce);
    mock.Verify(_ => _.EndStuff(), Times.AtLeastOnce());
}
Run Code Online (Sandbox Code Playgroud)

现在,理想情况下,这将被视为测试框架代码,因为您基本上是在测试 aBackgroundService在运行时的行为是否符合预期,但它应该足以说明如何单独测试此类服务

  • @RB。如果您有更好的建议,为什么不将其作为答案呢? (6认同)
  • 但这种方法存在一些问题。首先,基于时间的单元测试本质上是脆弱的。其次,如果出现问题,您将不会收到有意义的异常消息 - 您只会发现验证调用失败,并且需要使用调试器进行单步执行。一些更好的方法:(1)将尽可能多的服务代码放入可测试的类中,并将该类的实例注入到“BackgroundService”中。然后你只需要证明注入的类被调用(2)使用反射来调用ExecuteAsync方法。 (4认同)