Search code examples
c#asp.net-core.net-coremstestasp.net-core-hosted-services

Integration Test for Hosted Service in .NET Core


I have a QueueTask Hosted service (.NET Core's new background service) that I'd like to test. My queuedHosted service looks like so:

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)}.");
            }
        }
    }
}

It just reads tasks from the queue and executes them as they come in. I've already verified that the Hosted Service is working in Production. I wrote a test for it like so:

[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);
}

However, my callback is never hit. How can I get my task to execute on the Background Service?

Edit

I was mimicking the startup, and assumed my background service would just work, but apparently my service is never started.

How is the Hosted Service normally Started from .NET Core?


Solution

  • Hosted services are started by the framework as part of the WebHost's start process

    // Fire IHostedService.Start
    await _hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false);
    

    Source

    via the HostedServiceExecutor which would take a collection of all the registered IHostedService, enumerate them and start them in turn

    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);
        }
    }
    

    Source

    But since you are testing the hosted service on its own, you have to act as the framework and start the service yourself.

    [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);
    }