.NET Core中的托管服务单元测试

6

我在.NET Core中通过托管服务实现了一个后台任务。这个类中几乎没有什么逻辑:

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;
        }
    }
}

我的创业公司的样子:

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

我该如何对 DoWork 方法中的逻辑进行单元测试?问题在于托管服务由框架管理,我不知道如何隔离这个类。


据我所知,没有简单的方法来对私有函数进行单元测试。受保护的函数至少可以通过继承该类并包装该函数来进行测试,但是私有函数无法这样做。您必须通过启动异步后台线程来进行间接测试。 - fredrik
3个回答

7
我不太确定您所谓的隔离类是什么意思。这些并不神奇,ASP.NET Core只是实例化带有任何必需依赖项的类,然后在应用程序关闭时调用StartAsync,稍后调用StopAsync. 您完全可以自己手动完成这些。
换句话说,为了进行单元测试,您需要模拟依赖项,实例化类,并对其调用StartAsync。但是,我认为总体而言,托管服务更适合进行集成测试。您可以将任何真实工作分解为一个更简单的帮助程序类以进行单元测试,然后仅运行服务上的集成测试以确保其通常执行所需操作。

2
请问您是否有仓库/代码示例可以与我们分享? - Kevy Granero

0

使用以下方法从依赖注入容器中检索托管服务:

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

0

我有一个BackgroundService的子类,我想对它进行单元测试。这个MS测试工作得很好!

[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;
}

这与运行时的工作方式非常相似。ASP.NET框架将调用StartAsync。然后在您的应用程序生命周期的某个时间点,将执行后台作业(在我的情况下是QueueBackgroundWorkItem)。您可以在DoSomething中设置断点并查看它是否被实际调用。

以这种方式,您可以单元测试仅BackgroundService而不测试任何其他应用程序逻辑。


@havij,这段代码应该适用于你。如果你不想测试IIndexService,你可以使用Moq或NSubsistute来传入一个虚假的服务。 - Jess
1
我还了解到,如果在调用StartAsync之前排队工作项,则似乎根本不会返回。 - Jess

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接