如何在集成测试中停止.NET Core的HostedService

8

我有一个.NET Core web API项目,因为某些原因,在该项目中创建了一个后台服务,并在应用程序启动时开始运行该后台服务。 因此,我们创建了一个BackgroundWorkerService,该服务继承自 BackgroundService(Microsoft.Extensions.Hosting),如下所示:

public class BackgroundWorkerService : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await DoWork(stoppingToken);
    }

    public override async Task StartAsync(CancellationToken cancellationToken)
    {
        await ExecuteAsync(cancellationToken);
    }

    public override Task StopAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }
}

为了在应用程序启动时运行它,我将后台服务添加到 Program.cs 中托管的服务中:

.ConfigureServices(services =>
                services.AddHostedService<BackgroundWorkerService>());

现在,我们需要创建一个集成测试,并且在运行集成测试时停止后台服务。

有人知道如何在集成测试中停止它吗?我已经尝试从ConfigureTestServices中移除服务,但没有成功,当集成测试开始时后台服务仍在运行。


你为什么要重写 StartAsyncStopAsync 方法? - Stephen Cleary
因为我们希望为我们的业务逻辑拥有一个单独的服务,该服务需要从StartAsync执行。 - CF7
我的观点是,你应该能够轻松地覆盖 ExecuteAsync 方法。StartAsyncStopAsync 方法已经被 BackgroundService 类型实现了。 - Stephen Cleary
谢谢@StephenCleary,我也可以做到。但是,你有没有想过如何在集成测试中停止后台服务?集成测试实现了WebApplicationFactory,它将创建一个客户端,然后客户端默认启动后台服务。这不是我们想要的,那么我们该如何在集成测试中停止它呢? - CF7
不行。你可能需要某种外部信号(如Redis / CosmosDb租约)来防止后台服务运行。 - Stephen Cleary
显示剩余2条评论
4个回答

11
根据您在问题中提到的,您应该能够在ConfigureTestServices中删除服务。然而,关键在于确切地指定要删除哪个服务。
根据我的经验,您需要查找相关的ServiceDescriptor而不是使用期望值创建新的ServiceDescriptor(请参见下面的注释原因)。我使用LINQ和服务的实现类型(在您的情况下是BackgroundWorkerService)来轻松查找现有的ServiceDescriptor。然后我可以将其从列表中删除,这意味着在CreateClient()调用WebApplicationFactory时,该服务不会被启动。
大致如下:
builder.ConfigureTestServices(services =>
{
    var descriptor = services.Single(s => s.ImplementationType == typeof(BackgroundWorkerService));
    services.Remove(descriptor);
}

这种方法的一个非常好的好处是,它使测试逻辑不会出现在生产代码中,而完全出现在测试的设置/固定装置中。
关于IServiceCollection的移除注意事项:经过一些探索,我认为这是因为ServiceDescriptor没有提供除Object.Equals以外的相等或比较方法,后者会退回到引用相等。因此,即使在新的ServiceDescriptor中所有值都相同,它们也将是不同的对象,因此不会被找到并从服务列表中删除。

有趣的是,还有一个 services.RemoveAll<T> 方法。不幸的是,这个方法存在缺陷 - 当我将服务注册为 class BackgroundWorkerService : BackgroundService 并调用 services.RemoveAll<BackgroundWorkerService> 时,该服务仍然存在... - Simopaa

2

我之前遇到了同样的问题,所以我扩展了WebApplicationFactory类,并在重写CreateHost方法时删除了托管服务。

    public class CustomWebApplicationFactory<T> : WebApplicationFactory<T>
    where T : class
{
    protected override IHost CreateHost(IHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            var descriptor = services.Single(s => s.ImplementationType == typeof(BackgroundService));
            services.Remove(descriptor);
        });
        return base.CreateHost(builder);
    }
}

2
其他答案都是正确的,但我想补充一点,因为我发现为什么 RemoveAll 仍然可以使用。
根据 dotnet 源代码,RemoveAll 方法使用 ServiceType 而不是 ImplementationType 来查找服务。
/// <summary>
/// Removes all services of type <paramref name="serviceType"/> in <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="collection">The <see cref="IServiceCollection"/>.</param>
/// <param name="serviceType">The service type to remove.</param>
/// <returns>The <see cref="IServiceCollection"/> for chaining.</returns>
public static IServiceCollection RemoveAll(this IServiceCollection collection, Type serviceType)
{
    ThrowHelper.ThrowIfNull(serviceType);

    for (int i = collection.Count - 1; i >= 0; i--)
    {
        ServiceDescriptor? descriptor = collection[i];
        if (descriptor.ServiceType == serviceType)
        {
            collection.RemoveAt(i);
        }
    }

    return collection;
}

这意味着我们不能使用实际的服务类型BackgroundWorkerService(如问题中所示),而是要使用它注册时的类型IHostedService
这意味着这段代码可以用来彻底清除所有后台工作进程。
services.RemoveAll<IHostedService>();

不幸的是,这也会删除一些由asp.net框架注册的工作人员,他们需要重新引入。

1

我找到了一个解决方案,可以将后台服务注册放在以下条件中。

编辑Program.cs文件,将您的后台服务注册部分更改如下:

.ConfigureServices(services =>
        {
            if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != "INTEGRATION")
            {
                services.AddHostedService<BackgroundWorkerService>();
            }
        });

然后尝试将变量更改为您需要的“INTEGRATION”。


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