单元测试使用System.Threading.Timer的异步方法

时间:2019-09-12 14:04:37

标签: c# unit-testing nunit moq

在.NET Core中,后台任务实现为IHostedService。这是我的托管服务:

public interface IMyService {
    void DoStuff();
}

public class MyHostedService : IHostedService, IDisposable
{
    private const int frequency;

    private readonly IMyService myService;

    private Timer timer;

    public MyHostedService(IMyService myService, Setting s)
    {
        this.myService = myService;
        frequency = s.Frequency;
    }

    public void Dispose()
    {
        this.timer?.Dispose();
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        this.timer = new Timer(this.DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(this.frequency));
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        this.timer?.Change(Timeout.Infinite, 0);
        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        try
        {
            this.myService.DoStuff();
        }
        catch (Exception e)
        {
            // log
        }
    }
}

我正在尝试对该类进行单元测试,而我想要做的就是确保在调用DoStuff方法时调用StartAsync。这是我的单元测试:

[TestFixture]
public class MyHostedServiceTests
{
    [SetUp]
    public void SetUp()
    {
        this.myService = new Mock<IMyService>();

        this.hostedService = new MyHostedService(this.myService.Object, new Setting { Frequency = 60 });
    }

    private Mock<ImyService> myService;
    private MyHostedService hostedService;

    [Test]
    public void StartAsync_Success()
    {
        this.hostedService.StartAsync(CancellationToken.None);

        this.myService.Verify(x => x.DoStuff(), Times.Once);
    }
}

为什么会失败?

1 个答案:

答案 0 :(得分:1)

失败,因为异步代码在与正在验证预期行为的代码不同的线程上执行。那就是事实,即在计时器有时间之前调用了验证代码。

在测试异步方法时,大多数情况下的测试也应该是异步的。

在这种情况下,您还需要花一些时间来允许计时器调用。

使用Task.Delay为计时器提供足够的时间来执行其功能。

例如

[TestFixture]
public class MyHostedServiceTests {
    [SetUp]
    public void SetUp() {
        this.myService = new Mock<IMyService>();
        this.setting = new Setting { Frequency = 2 };
        this.hostedService = new MyHostedService(this.myService.Object, setting);
    }

    private Mock<ImyService> myService;
    private MyHostedService hostedService;
    private Setting setting;

    [Test]
    public async Task StartAsync_Success() {

        //Act
        await this.hostedService.StartAsync(CancellationToken.None);
        await Task.Delay(TimeSpan.FromSeconds(1));    
        await this.hostedService.StopAsync(CancellationToken.None);

        //Assert
        this.myService.Verify(x => x.DoStuff(), Times.Once);
    }
}

以上示例使用较短的频率来测试预期的行为