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

问题描述 投票:0回答:3

我正在使用 .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);
    }

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

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

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

c# .net-core asp.net-core-hosted-services
3个回答
26
投票

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

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

下面使用 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
在运行时的行为是否符合预期,但它应该足以说明如何单独测试此类服务


2
投票

上述解决方案对我来说不起作用。因为后台服务会无限运行。 我的解决方案使用 CancellationToken 并创建一个线程在一段时间后取消它。 代码如下:

CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
new Thread(async () =>
{
     Thread.CurrentThread.IsBackground = true;
     await Task.Delay(500);
     hostedService.StopAsync(token);
}).Start();

await hostedService.StartAsync(token)

0
投票

您可以在集成测试项目中创建另一个继承自原始服务的服务来执行 BackGroundService 的受保护方法。

  public class SpyService : MyOriginalBackGroundService
  {
  public bool WasExecuted { get; private set; } = false;

  public SpyDocumentCsvExportService(
      IOneOfMyCkass oneOfMyClass,
      ILog log)
      : base(oneOfMyClass,log) {}

  protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  {
      WasExecuted = true;
      await base.ExecuteAsync(stoppingToken);
  }}

在集成测试类中,您可以在初始化后调用它。

await spyService.StartAsync(CancellationToken.None);
© www.soinside.com 2019 - 2024. All rights reserved.