Reputation: 169
I have .NET Core web API project, for some reasons, we created a background service in this project and start running the background service while the application is started.
So, we created a BackgroundWorkderService
, which inherited from BackgroundService (Microsoft.Extensions.Hosting) like below:
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;
}
}
To run it while the application starts, I added the background service to the hosted service in Program.cs as below:
.ConfigureServices(services =>
services.AddHostedService<BackgroundWorkerService>());
Now, we need to create an integration test, and we want to stop the background service while we are running the integration test.
Does anyone know how to stop it in the integration test? I have tried to remove the service from ConfigureTestServices, but no luck with it, the background service still runs when the integration test starts.
Upvotes: 13
Views: 6763
Reputation: 51
I faced same issue so I've extended WebApplicationFactory class and removed hosted service before by overriding 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);
}
}
Upvotes: 4
Reputation: 488
Other answers are correct, but I wanted to add a little supplement as I found why RemoveAll
could still be used.
According to the dotnet source code the RemoveAll method uses ServiceType
and not ImplementationType
to find the service.
/// <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;
}
This means that we can't use actual service type of BackgroundWorkerService
(as in the question) but instead the type it was registered with which is IHostedService
This means that this code can be used to nuke all background workers from existence.
services.RemoveAll<IHostedService>();
Unfortunately this removes also some workers that are registered by the asp.net framework, and they need to be reintroduced.
Upvotes: 3
Reputation: 429
You should be able to, as you mentioned in the question, remove the service in ConfigureTestServices
. However, exactly how we specify which service to remove is the key here.
In my experience, you need to find the ServiceDescriptor
in question rather than create a new one with the expected values (see note below for why). I use LINQ and the implementation type of the service (in your case, BackgroundWorkerService
) to easily find the existing ServiceDescriptor
. Then I can remove it from the list, which means it won't be started when CreateClient()
is called on the WebApplicationFactory
.
Looks something like this:
builder.ConfigureTestServices(services =>
{
var descriptor = services.Single(s => s.ImplementationType == typeof(BackgroundWorkerService));
services.Remove(descriptor);
}
A really nice benefit of this approach is that it keeps test logic out of the production code and entirely in the setup/fixture for your tests.
Note on IServiceCollection
removals: With a bit of poking, I believe this is because ServiceDescriptor
doesn't provide an equality or comparison method other than Object.Equals
, which falls back to reference equality. So even if all the values are the same in a new ServiceDescriptor
, they would different objects and thus won't be found and removed from the service list.
Upvotes: 14
Reputation: 169
I found a solution to put the background service register in a condition as below.
Edit the Program.cs file as below in the section of registering your background service:
.ConfigureServices(services =>
{
if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != "INTEGRATION")
{
services.AddHostedService<BackgroundWorkerService>();
}
});
Then try to change the variable to be INTEGRATION from where you need.
Upvotes: 1