我正在使用 .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 进行解释的,我真的不知道是否可以以同样的方式测试我的。
我只想知道我的代码是否被执行。我不想要任何具体的结果。 你知道我如何测试它吗?
您应该仍然能够遵循与链接答案类似的格式。
模拟依赖项并注入它们,调用被测试的方法并断言预期的行为。
下面使用 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
在运行时的行为是否符合预期,但它应该足以说明如何单独测试此类服务
上述解决方案对我来说不起作用。因为后台服务会无限运行。 我的解决方案使用 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)
您可以在集成测试项目中创建另一个继承自原始服务的服务来执行 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);