如何在单元测试中模拟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'
相关产品推荐
相关产品推荐

