Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I write unit test for my background service?

I'm working with the HostBuilder in .NET Core (not the WebHost !).

I have one Hosted Service running in my application that overrides the ExecuteAsync/StopAsync methods of the background Service and I want to unit test it.

Here is my HostedService:

public class DeviceToCloudMessageHostedService : BackgroundService
{
    private readonly IDeviceToCloudMessageService _deviceToCloudMessageService;
    private readonly AppConfig _appConfig;

    public DeviceToCloudMessageHostedService(IDeviceToCloudMessageService deviceToCloudMessageService, IOptionsMonitor<AppConfig> appConfig)
    {
        _deviceToCloudMessageService = deviceToCloudMessageService;
        _appConfig = appConfig.CurrentValue;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            await _deviceToCloudMessageService.DoStuff(stoppingToken);
            await Task.Delay(_appConfig.Parameter1, stoppingToken);
        }
    }
    
    public override Task StopAsync(CancellationToken cancellationToken)
    {
        Log.Information("Task Cancelled");
        _deviceToCloudMessageService.EndStuff();
        return base.StopAsync(cancellationToken);
    }

I already found this post: Integration Test for Hosted Service in .NET Core

But it's explained for a QueuedBackgroundService and I don't really know if I can test mine the same way.

I just want to know if my code is executed. I don't want any specific result. Do you have any idea of how I can test it?

like image 984
Xyluun Avatar asked Jun 18 '19 08:06

Xyluun


People also ask

How do you start writing a unit test?

Follow Arrange, Act, Assert The AAA is a general approach to writing more readable unit tests. In the first step, you arrange things up for testing. It's where you set variables, instantiate objects, and do the rest of the required setup for the test to run. During this step, you also define the expected result.

What are the three steps in a unit test?

The idea is to develop a unit test by following these 3 simple steps: Arrange – setup the testing objects and prepare the prerequisites for your test. Act – perform the actual work of the test. Assert – verify the result.

How do you record a unit test?

To record unit test results to the pipeline's run log: Run unit tests in a pipeline step that produce a results file in junit format. Save the test results file using the save_tests utility function.

What should be covered by unit tests?

Unit tests should validate all of the details, the corner cases and boundary conditions, etc. Component, integration, UI, and functional tests should be used more sparingly, to validate the behavior of the APIs or application as a whole.


1 Answers

You should still be able to follow a similar format as the linked answer.

Mock the dependencies and inject them, invoke the methods under test and assert the expected behavior.

The following uses Moq to mock the dependencies along with ServiceCollection to do the heavy lifting of injecting the dependencies.

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

[TestMethod]
public async Task DeviceToCloudMessageHostedService_Should_DoStuff() {
    //Arrange
    IServiceCollection services = new ServiceCollection();
    services.AddSingleton<IHostedService, DeviceToCloudMessageHostedService>();
    //mock the dependencies for injection
    services.AddSingleton(Mock.Of<IDeviceToCloudMessageService>(_ =>
        _.DoStuff(It.IsAny<CancellationToken>()) == Task.CompletedTask
    ));
    services.AddSingleton(Mock.Of<IOptionsMonitor<AppConfig>>(_ =>
        _.CurrentValue == Mock.Of<AppConfig>(c => 
            c.Parameter1 == TimeSpan.FromMilliseconds(1000)
        )
    ));
    var serviceProvider = services.BuildServiceProvider();
    var hostedService = serviceProvider.GetService<IHostedService>();

    //Act
    await hostedService.StartAsync(CancellationToken.None);
    await Task.Delay(1000);//Give some time to invoke the methods under test
    await hostedService.StopAsync(CancellationToken.None);

    //Assert
    var deviceToCloudMessageService = serviceProvider
        .GetRequiredService<IDeviceToCloudMessageService>();
    //extracting mock to do verifications
    var mock = Mock.Get(deviceToCloudMessageService);
    //assert expected behavior
    mock.Verify(_ => _.DoStuff(It.IsAny<CancellationToken>()), Times.AtLeastOnce);
    mock.Verify(_ => _.EndStuff(), Times.AtLeastOnce());
}

Now, ideally this would count as testing framework code since you are basically testing that a BackgroundService behaves as expected when run, but it should demonstrate enough about how one would test such a service in isolation

like image 189
Nkosi Avatar answered Sep 17 '22 14:09

Nkosi