Reputation: 1170
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
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
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
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
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