.NET Core中托管服务的集成测试

27

我有一个队列任务托管服务(.NET Core的新后台服务),我想测试一下。我的队列托管服务看起来像这样:

public QueuedHostedService(IServiceProvider serviceProvider, IBackgroundTaskQueue taskQueue, ILoggerFactory loggerFactory)
{
    TaskQueue = taskQueue;
    _logger = loggerFactory.CreateLogger<QueuedHostedService>();
    _serviceProvider = serviceProvider;
}

protected async override Task ExecuteAsync(CancellationToken stoppingToken)
{
    using (var scope = _serviceProvider.CreateScope())
    {
        while (false == stoppingToken.IsCancellationRequested)
        {
            var workItem = await TaskQueue.DequeueAsync(stoppingToken);
            try
            {
                await workItem(scope.ServiceProvider, stoppingToken);
            }
            catch (Exception ex)
            {
                this._logger.LogError(ex, $"Error occurred executing {nameof(workItem)}.");
            }
        }
    }
}

它只是从队列中读取任务,并按照它们到达的顺序执行它们。我已经验证了托管服务在生产环境中正在工作。我为此编写了一个测试,如下所示:

[TestMethod]
public async Task Verify_Hosted_Service_Executes_Task()
{
    IServiceCollection services = new ServiceCollection();
    services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
    services.AddHostedService<QueuedHostedService>();
    services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
    var serviceProvider = services.BuildServiceProvider();

    var backgroundQueue = serviceProvider.GetService<IBackgroundTaskQueue>();

    var isExecuted = false;
    backgroundQueue.QueueBackgroundWorkItem(async (sp, ct) => {
        isExecuted = true;
    });

    await Task.Delay(10000);
    Assert.IsTrue(isExecuted);
}

然而,我的回调函数从未执行。我该如何让我的任务在后台服务上执行?

编辑

我在模拟启动,并假设我的后台服务会正常工作,但显然我的服务从未启动。

在 .NET Core 中 Hosted Service 通常是如何启动的?


关于“如何启动托管服务?”,您应该使用GenericHost来运行您的IHostedService。您还需要配置服务依赖项。 - Filip
1个回答

35

托管服务是由框架作为Web主机启动过程的一部分启动的。

// Fire IHostedService.Start
await _hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false);
通过HostedServiceExecutor来启动所有已注册的IHostedService集合,并依次枚举并启动它们。

源代码

public HostedServiceExecutor(ILogger<HostedServiceExecutor> logger, IEnumerable<IHostedService> services)
{
    _logger = logger;
    _services = services;
}

public async Task StartAsync(CancellationToken token)
{
    try
    {
        await ExecuteAsync(service => service.StartAsync(token));
    }
    catch (Exception ex)
    {
        _logger.ApplicationError(LoggerEventIds.HostedServiceStartException, "An error occurred starting the application", ex);
    }
}

来源

但由于您正在单独测试托管服务,因此必须扮演框架的角色并自己启动服务。

[TestMethod]
public async Task Verify_Hosted_Service_Executes_Task() {
    IServiceCollection services = new ServiceCollection();
    services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
    services.AddHostedService<QueuedHostedService>();
    services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
    var serviceProvider = services.BuildServiceProvider();

    var service = serviceProvider.GetService<IHostedService>() as QueuedHostedService;

    var backgroundQueue = serviceProvider.GetService<IBackgroundTaskQueue>();

    await service.StartAsync(CancellationToken.None);

    var isExecuted = false;
    backgroundQueue.QueueBackgroundWorkItem(async (sp, ct) => {
        isExecuted = true;
    });

    await Task.Delay(10000);
    Assert.IsTrue(isExecuted);

    await service.StopAsync(CancellationToken.None);
}

1
我不得不使用 var service = serviceProvider.GetService<IHostedService>(); 而不是 QueuedHostedService 来获取服务以便填充。 - user1568891
1
@user1568891 那么问题就出在我的代码上了。经过进一步的审查,您所描述的行为是正确的,因为我没有看到该类与服务集合进行直接注册。 - Nkosi
2
您还可以使用新服务(特别是在注册了多个IHostedInstaces的情况下非常有用) serviceProvider.GetServices<IHostedService>().OfType<YourType>().Single(); - miraco
1
services.AddSingleton<ILoggerFactory, NullLoggerFactory>();这个对我没用,我使用了 services.AddLogging(config => config.AddDebug()); - yousif
在我的情况下,托管服务会立即启动,而不像通常那样需要调用service.StartAsync。 - Roel
显示剩余2条评论

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