Moq验证异常:预期调用5次检测为0次,但实际存在调用
我查阅相关资料后,在使用Moq进行单元测试时仍遇到以下异常:
Moq.MockException : 预期对Mock的调用恰好5次,
但实际为0次: _ =>_.SendAsync(Mock<IClientProxy:2>.Object, It.IsAny<string>(), It.IsAny<List<Notification>>(), CancellationToken)已执行的调用:
MockIClientContextCommand:2 (_):
IClientContextCommand.SendAsync(MockIClientProxy:2.Object, "FooBarNotifications", [Notification], CancellationToken)
IClientContextCommand.SendAsync(MockIClientProxy:2.Object, "FooBarNotifications", [Notification], CancellationToken)
IClientContextCommand.SendAsync(MockIClientProxy:2.Object, "FooBarNotifications", [Notification], CancellationToken)
IClientContextCommand.SendAsync(MockIClientProxy:2.Object, "FooBarNotifications", [Notification], CancellationToken)
IClientContextCommand.SendAsync(MockIClientProxy:2.Object, "FooBarNotifications", [Notification], CancellationToken)
IClientContextCommand.SendAsync(MockIClientProxy:2.Object, "FooBarNotifications", [Notification], CancellationToken)
IClientContextCommand.SendAsync(MockIClientProxy:2.Object, "FooBarNotifications", [Notification], CancellationToken)
IClientContextCommand.SendAsync(MockIClientProxy:2.Object, "FooBarNotifications", [Notification], CancellationToken)
IClientContextCommand.SendAsync(MockIClientProxy:2.Object, "FooBarNotifications", [Notification], CancellationToken)
IClientContextCommand.SendAsync(MockIClientProxy:2.Object, "FooBarNotifications", [Notification], CancellationToken)堆栈跟踪: Mock.Verify(Mock mock, LambdaExpression expression,
Times times, String failMessage)line337
Mock1.Verify[TResult](Expression1 expression, Times times)line34
FooBarServiceTests.SendAsync_ShouldBe_Called_N_Times()line105
相关代码
单元测试代码
private readonly Mock<IClientContextCommand> _mockClientContextCommand; // 构造函数 public FooTests() { _mockClientContextCommand = new Mock<IClientContextCommand>(); _mockClientContextCommand.Setup(x => x.SendAsync(_mockClientProxy.Object, It.IsAny<string>(), It.IsAny<Notification[]>(), CancellationToken.None)); services.AddSingleton(_mockClientContextCommand.Object); } [Fact] public async Task SendAsync_ShouldBe_Called_N_Times() { await _hostedService.StartAsync(CancellationToken.None); await Task.Delay(1); await _hostedService.StopAsync(CancellationToken.None); int times = GetNotifications().Count(); _mockClientContextCommand.Verify( _ => _.SendAsync(_mockClientProxy.Object, It.IsAny<string>(), It.IsAny<List<Notification>>(), CancellationToken.None), Times.Exactly(times)); }
业务方法代码
private async Task Send(CancellationToken stoppingToken) { // ... 其他代码已省略 foreach (IGrouping<string, Notification> group in messages.GroupBy(m => m.To)) { IClientProxy connection = _hubContext.Clients.User(group.Key); List<Notification> notifies = group.ToList(); await _clientContextCommand.SendAsync(connection, "FooBarNotifications", notifies, stoppingToken); } }
接口定义
public interface IClientContextCommand { Task SendAsync(IClientProxy clientProxy, string method, object? arg1, CancellationToken cancellationToken = default); }
1. ClientProxy实例不匹配
测试验证时指定的_mockClientProxy.Object,和业务代码中_hubContext.Clients.User(group.Key)返回的IClientProxy实例不是同一个对象。Moq的调用匹配严格依赖实例引用,因此即使实际调用了10次,也会因参数不匹配被判定为0次符合条件的调用。
解决办法:
在测试构造函数或测试方法中,给_hubContext.Clients.User添加Setup,让它返回你的_mockClientProxy.Object,确保业务代码拿到的ClientProxy和验证时指定的是同一个实例:
// 假设你已声明_mockHubContext实例 _mockHubContext.Setup(h => h.Clients.User(It.IsAny<string>())) .Returns(_mockClientProxy.Object);
2. CancellationToken参数匹配错误
验证时硬编码了CancellationToken.None,但业务代码传入的是stoppingToken。Moq对CancellationToken的匹配是按实例引用而非token值,这会导致参数匹配失败,验证时找不到符合条件的调用。
解决办法:
验证时用It.IsAny<CancellationToken>()代替CancellationToken.None,忽略具体的token实例:
_mockClientContextCommand.Verify( _ => _.SendAsync(_mockClientProxy.Object, It.IsAny<string>(), It.IsAny<List<Notification>>(), It.IsAny<CancellationToken>()), Times.Exactly(times));
3. Setup参数匹配优化(可选)
当前Setup中第三个参数用的是It.IsAny<Notification[]>(),但业务代码传入的是List<Notification>。虽然接口参数是object?不会影响验证,但为了让Setup的行为匹配更准确,建议改为:
_mockClientContextCommand.Setup(x => x.SendAsync(_mockClientProxy.Object, It.IsAny<string>(), It.IsAny<object>(), It.IsAny<CancellationToken>()));
额外建议
测试中的Task.Delay(1)不可靠,建议替换为更稳定的等待逻辑,比如使用信号量确保业务逻辑执行完成后再调用StopAsync,避免因延迟时间不足导致业务代码未执行完毕就停止服务,影响调用次数统计。
内容的提问来源于stack exchange,提问作者Learner

