CF7
CF7

Reputation: 169

.NET Core stop HostedService in the Integration test

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

Answers (4)

Rupesh
Rupesh

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

Filip Franik
Filip Franik

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

Cameron Bielstein
Cameron Bielstein

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

CF7
CF7

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

Related Questions