Unit test .net 6 console app using IHostApplicationLifetime

huangapple go评论72阅读模式
英文:

Unit test .net 6 console app using IHostApplicationLifeTime

问题

我有一个类(`CronClass`)继承自`IHostedService`,其中包括两个方法:`StartAsync(CancellationToken)``StopAsync(CancellationToken)`。

你如何使用Moq库来进行单元测试以验证`StartAsync`方法是否执行了代码
例如
```csharp
public class CronClass: IHostedService
{
    private readonly IHostedApplicationLifetime applicationLifetime;
    private readonly IService service;
    // IHostedApplicationLifetime/IService 通过构造函数进行依赖注入

    public Task StartAsync(CancellationToken cancellationToken)
    {
        applicationLifeTime.ApplicationStarted.Register(() =>
        {
            Task.Run(async () =>
            {
                log.LogInformation("Cron Started");
                await service.Process();
            });
        });
    }
    //...
}
英文:

I have a class (CronClass) that inherits from IHostedService with 2 methods which are StartAsync(CancellationToken) and StopAsync(CancellationToken).

How do you go about unit testing the StartAsync method to verify that the code was executed, using Moq Library?
For example:

public class CronClass: IHostedService
{
    private readonly IHostedApplicationLifetime applicationLifetime;
    private readonly IService service;
    // IHostedApplicationLifetime/IService are injected DI to via the constructor
    public Task StartAsync(CancellationToken cancellationToken)
    {
        applicationLifeTime.ApplicationStarted.Register(() =>
        {
            Task.Run(async () =>
            {
                log.LogInformation("Cron Started");
                await service.Process();
            });
        });
    }
    //...
}

答案1

得分: 2

以下是代码部分的中文翻译:

我会从创建 `IHostApplicationLifetime` 的模拟开始

public class MockHostApplicationLifetime : IHostApplicationLifetime, IDisposable
{
    internal readonly CancellationTokenSource _ctsStart = new CancellationTokenSource();
    internal readonly CancellationTokenSource _ctsStopped = new CancellationTokenSource();
    internal readonly CancellationTokenSource _ctsStopping = new CancellationTokenSource();
    public MockHostApplicationLifetime()
    {
    }
    public void Started()
    {
        _ctsStart.Cancel();
    }
    CancellationToken IHostApplicationLifetime.ApplicationStarted => _ctsStart.Token;
    CancellationToken IHostApplicationLifetime.ApplicationStopping => _ctsStopping.Token;
    CancellationToken IHostApplicationLifetime.ApplicationStopped => _ctsStopped.Token;
    public void Dispose()
    {
        _ctsStopped.Cancel();
        _ctsStart.Dispose();
        _ctsStopped.Dispose();
        _ctsStopping.Dispose();
    }
    public void StopApplication()
    {
        _ctsStopping.Cancel();
    }
}

在你的单元测试中创建 `IService` 的模拟。创建 `CronClass` 的实例并调用 `cronClass.StartAsync`。然后启动 `MockHostApplicationLifetime`。这将触发注册的回调函数 `ApplicationStarted.Register`。然后验证是否调用了 `Process()`。

你在 `Register` 方法中启动了任务,所以可能会发生在单元测试完成之前任务被创建并调用了 `service.Process` 的情况。在这种情况下,我建议在验证之前等待一段时间。

[Test]
public async Task Test1()
{
    var hal = new MockHostApplicationLifetime();
    var mockService = new Mock<IService>();
    var cronClass = new CronClass(hal, mockService.Object);
    await cronClass.StartAsync(CancellationToken.None);
    hal.Started();
    // 可能不需要,但即使没有延迟,测试也通过了
    // await Task.Delay(500);
    mockService.Verify(mock => mock.Process());
}
英文:

I would start with creating a mock of IHostApplicationLifetime

public class MockHostApplicationLifetime : IHostApplicationLifetime, IDisposable
{
    internal readonly CancellationTokenSource _ctsStart = new CancellationTokenSource();
    internal readonly CancellationTokenSource _ctsStopped = new CancellationTokenSource();
    internal readonly CancellationTokenSource _ctsStopping = new CancellationTokenSource();
    public MockHostApplicationLifetime()
    {
    }
    public void Started()
    {
        _ctsStart.Cancel();
    }
    CancellationToken IHostApplicationLifetime.ApplicationStarted => _ctsStart.Token;
    CancellationToken IHostApplicationLifetime.ApplicationStopping => _ctsStopping.Token;
    CancellationToken IHostApplicationLifetime.ApplicationStopped => _ctsStopped.Token;
    public void Dispose()
    {
        _ctsStopped.Cancel();
        _ctsStart.Dispose();
        _ctsStopped.Dispose();
        _ctsStopping.Dispose();
    }
    public void StopApplication()
    {
        _ctsStopping.Cancel();
    }
}

In your unit test create a mock of IService. Create instance of CronClass and call cronClass.StartAsync. Then start MockHostApplicationLifetime. It will trigger registered callback ApplicationStarted.Register. Then verify that Process() was called.

You are starting the task in Register method, so it can happen that the unit test can finish before the task is created and service.Process is called. In that case I would wait some time before verification.

[Test]
public async Task Test1()
{
    var hal = new MockHostApplicationLifetime();
    var mockService = new Mock<IService>();
    var cronClass = new CronClass(hal, mockService.Object);
    await cronClass.StartAsync(CancellationToken.None);
    hal.Started();
    // maybe not needed, test passed without the delay
    //await Task.Delay(500);
    mockService.Verify(mock => mock.Process());
}

huangapple
  • 本文由 发表于 2023年2月16日 14:51:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/75468740.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定