Unit testing Hosted Services in .NET Core
Asked Answered
Y

3

7

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.

Yeast answered 3/9, 2019 at 15:20 Comment(1)
as far as I know there is no simple way to unit test private functions. protected could at least be tested by inheriting the class and wrapping the function - but private can't do that. You'd have to do indirect testing by starting the async background thread.Drinkable
W
7

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.

Weidman answered 3/9, 2019 at 15:26 Comment(1)
Would you have any repository/code examples to share with us please?Frazier
T
0

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;
. . .
Tympanites answered 3/3, 2022 at 0:11 Comment(0)
F
0

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.

Fante answered 16/8, 2022 at 14:22 Comment(2)
@havij, this code should work for you. If you don't want to test IIndexService, you can use Moq or NSubsistute to pass in a fake service.Fante
I also learned that StartAsync does not seem to return at all if you queue your work item BEFORE calling StartAsync.Fante

© 2022 - 2024 — McMap. All rights reserved.