You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在单元测试中模拟PeriodicTimer?求可行方案(偏好NSubstitute)

如何在单元测试中模拟PeriodicTimer的计时(无需等待真实时间)

完全可行,最佳实践是通过抽象接口包装PeriodicTimer——因为它是密封类,无法直接Mock,这样就能在单元测试中完全控制计时逻辑,无需等待真实时间。以下是具体实现方案,包含NSubstitute示例:

步骤1:定义抽象接口

先创建一个与PeriodicTimer核心方法对齐的抽象接口:

public interface IPeriodicTimer
{
    ValueTask<bool> WaitForNextTickAsync(CancellationToken cancellationToken);
}

步骤2:实现真实的包装器

编写包装类,封装系统自带的PeriodicTimer:

public class PeriodicTimerWrapper : IPeriodicTimer
{
    private readonly PeriodicTimer _timer;

    public PeriodicTimerWrapper(TimeSpan interval)
    {
        _timer = new PeriodicTimer(interval);
    }

    public ValueTask<bool> WaitForNextTickAsync(CancellationToken cancellationToken)
    {
        return _timer.WaitForNextTickAsync(cancellationToken);
    }
}

步骤3:修改待测试类

调整Example类的依赖,注入IPeriodicTimer而非直接实例化PeriodicTimer,同时保留生产环境的构造逻辑:

public class Example : BackgroundService
{
    private readonly IPeriodicTimer _timer;

    // 生产环境构造函数:从配置读取间隔并创建真实计时器
    public Example(IConfiguration configuration)
        : this(new PeriodicTimerWrapper(TimeSpan.Parse(configuration["Interval"])))
    {
    }

    // 测试专用构造函数:注入模拟计时器
    public Example(IPeriodicTimer timer)
    {
        _timer = timer;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (await _timer.WaitForNextTickAsync(stoppingToken))
        {
            DoWork();
        }
    }

    // 将业务逻辑提取为可验证的方法(方便单元测试断言)
    public virtual void DoWork()
    {
        // 原有的业务操作逻辑
    }
}

步骤4:用NSubstitute编写单元测试

通过MockIPeriodicTimer,控制WaitForNextTickAsync的返回值,模拟多次计时触发,无需等待真实时间:

using NSubstitute;
using Xunit;

public class ExampleTests
{
    [Fact]
    public async Task ExecuteAsync_RunsDoWorkTwice_WhenTimerTicksTwice()
    {
        // 准备模拟计时器
        var mockTimer = Substitute.For<IPeriodicTimer>();
        // 模拟前两次计时触发(返回true),第三次返回false终止循环
        mockTimer.WaitForNextTickAsync(Arg.Any<CancellationToken>())
                 .Returns(
                     ValueTask.FromResult(true),
                     ValueTask.FromResult(true),
                     ValueTask.FromResult(false)
                 );

        // 初始化待测试类,注入模拟计时器
        var example = new Example(mockTimer);
        var stoppingTokenSource = new CancellationTokenSource();

        // 启动BackgroundService的执行逻辑
        var executeTask = example.StartAsync(stoppingTokenSource.Token);
        // 等待循环执行完成
        await executeTask;

        // 断言业务逻辑被执行了两次
        example.Received(2).DoWork();
    }
}

关于ValueTask的处理

无需深入IValueTaskSource,直接用ValueTask.FromResult()就能模拟返回值,NSubstitute完全支持ValueTask类型的返回值设置,非常简洁。

内容的提问来源于stack exchange,提问作者Viacheslav Rud'

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 19:01:12