havij
havij

Reputation: 1170

Unit testing Hosted Services in .NET Core

I have a background task implemented by hosted services in .NET Core. There is very little logic in this class:

public class IndexingService : IHostedService, IDisposable
{
    private readonly int indexingFrequency;

    private readonly IIndexService indexService;

    private readonly ILogger logger;

    private bool isRunning;

    private Timer timer;

    public IndexingService(ILogger<IndexingService> logger, IIndexService indexService, IndexingSettings indexingSettings)
    {
        this.logger = logger;
        this.indexService = indexService;

        this.indexingFrequency = indexingSettings.IndexingFrequency;
    }

    public void Dispose()
    {
        this.timer?.Dispose();
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        this.timer = new Timer(this.DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(this.indexingFrequency));
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        this.timer?.Change(Timeout.Infinite, 0);
        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        if (this.isRunning)
        {
            // Log
            return;
        }

        try
        {
            this.isRunning = true;
            this.indexService.IndexAll();
        }
        catch (Exception e)
        {
            // Log, The background task should never throw.
        }
        finally
        {
            this.isRunning = false;
        }
    }
}

and my Startup looks like:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHostedService<IndexingService>();
    services.AddTransient<IIndexService, IndexService>();
    // 'IndexingSettings' is read from appsetting and registered as singleton
}

How can I unit test the logic in DoWork method? The problem is that the hosted services are managed by the framework, and I don't know how to isolate this class.

Upvotes: 7

Views: 12488

Answers (4)

FinalFortune
FinalFortune

Reputation: 665

First result of googling testing a hosted service got me here, and chatgpt is too dumb. I need to write test for this because I used extension methods which add multiple services.

while the accepted answer is the way, this is how you get a background service quick:

public void Test()
{
    var services = new ServiceCollection();
    services.AddLogging(); // because you have ILogger in your constructor 
    services.AddHostedService<IndexingService>();

    var provider = services.BuildServiceProvider();
    var myService = provider.GetServices<IHostedService>().SingleOrDefault(s => s is IndexingService);

    Assert.NotNull(myService);
}

Upvotes: 0

Jess
Jess

Reputation: 25079

I have a sub class of BackgroundService which I want to unit test. This MS Test is working nicely!

[TestMethod()]
public async Task ExecuteAsync_TaskQueue()
{
    // Arrange
    var cancellationToken = new CancellationToken();
    await _sut.StartAsync(cancellationToken);

    // Act
    _sut.TaskQueue.QueueBackgroundWorkItem(DoSomething);
    await _sut.StopAsync(cancellationToken);

    // Assert
    Assert.AreEqual(_codeExecuted, true);
}

public Task DoSomething(CancellationToken cancellationToken)
{
    _codeExecuted = true;
    return Task.CompletedTask;
}

This is pretty similar to how it would work at run time. The ASP.NET framework will call StartAsync. Then at some point in your application lifetime a background job will execute (QueueBackgroundWorkItem in my case). You can put a breakpoint in DoSomething and see that it is actually called.

In this way you can unit test just the BackgroundService without testing any other of your application logic.

Upvotes: 0

JDawg
JDawg

Reputation: 9500

Retrieve the hosted service from the dependency injection container with the following:

. . .
services.AddHostedService<IndexingService>();
IServiceProvider serviceProvider = services.BuildServiceProvider();
IndexingService indexingService = serviceProvider.GetService<IHostedService>() as IndexingService;
. . .

Upvotes: 0

Chris Pratt
Chris Pratt

Reputation: 239290

Not sure what you mean about isolating the class. These aren't magical. ASP.NET Core just instantiates the class with any required dependencies, and then calls StartAsync, and later StopAsync on app shutdown. There's nothing of this that you cannot do yourself manually.

In other words, to unit test it, you'd mock the dependencies, instantiate the class, and call StartAsync on it. However, I think overall hosted services are a better candidate for integration testing. You can factor out any real work into a helper class that would be more simplistic to unit test, and then simply run an integration test on the service to ensure that it generally does what it's supposed to do.

Upvotes: 9

Related Questions