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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 12:05:21