25

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?

Pang
  • 9,564
  • 146
  • 81
  • 122
johnny 5
  • 19,893
  • 50
  • 121
  • 195
  • In regards to "How to start your hosted service?" You should use [GenericHost](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-2.2) to run your `IHostedService`. You will also need to configure service dependencies. – Filip Feb 12 '19 at 04:54

1 Answers1

32

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);
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • 1
    I had to use var service = serviceProvider.GetService(); instead of QueuedHostedService to get service to be populated. – user1568891 Oct 18 '18 at 17:48
  • @user1568891 `QueuedHostedService` was directly referring to the type they were using in their question. – Nkosi Oct 18 '18 at 17:54
  • @Nkosi When I just used the Type of my class that I also entered in AddHostedService, GetService returned null for me. It wouldn't return the type I entered in AddHostedService until I changed it to be IHostedService. Perhaps I was doing something else different though. – user1568891 Oct 18 '18 at 18:07
  • 1
    @user1568891 then the problem was with my code. Upon further review, the behavior you described is correct as I see no direct registration of that class with the service collection. – Nkosi Oct 18 '18 at 18:16
  • 2
    you can also get the new service with(especially yseful if you have multipleIHostedInstaces registered) serviceProvider.GetServices().OfType().Single(); – miraco Sep 09 '20 at 09:29
  • 1
    services.AddSingleton(); Didn't work for me, I used services.AddLogging(config => config.AddDebug()); – yousif Mar 02 '22 at 10:52
  • In my case the hosted service starts immediately without calling service.StartAsync as it does normally. – Roel Jun 01 '23 at 14:19