【问题标题】:How can I write unit test for my background service?如何为我的后台服务编写单元测试?
【发布时间】:2021-10-15 20:03:26
【问题描述】:

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

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

这是我的 HostedService:

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

我已经找到了这个帖子:Integration Test for Hosted Service in .NET Core

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

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

【问题讨论】:

标签: c# .net-core asp.net-core-hosted-services


【解决方案1】:

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

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

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

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

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

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

【讨论】:

  • 这种方法存在一些问题。首先,你有基于时间的单元测试,它们本质上是脆弱的。其次,如果出现问题,您将不会收到有意义的异常消息 - 您只会发现您的验证调用失败,并且您需要使用调试器逐步完成。一些更好的方法: (1) 将尽可能多的服务代码放入可测试的类中,并将该类的实例注入BackgroundService。那么你只需要证明注入的类被调用(2)使用反射调用ExecuteAsync方法。
  • @RB。如果您有更好的建议,何不将其作为答案?
  • hostedService.StartAsync 被阻塞,所以你的代码不起作用。
  • @RB。如果您打算使用这种方法,我建议您使用MediatR
猜你喜欢
  • 1970-01-01
  • 2020-12-21
  • 2019-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-28
  • 2019-01-18
  • 1970-01-01
相关资源
最近更新 更多