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

使用Moq统计C#递归方法SendMessage调用次数的异常问题

问题根因与解决方法

报错原因说明

  • 非虚SendMessage无法Verify:Moq基于动态生成目标类子类、重写虚成员实现调用拦截,非虚成员无法被重写,自然不支持Setup/Verify操作,属于Moq的底层实现限制。
  • 改为虚方法后仅统计到1次调用:未对虚SendMessage配置调用规则时,Moq默认不会走基类的递归逻辑,且内部调用未被代理拦截,导致计数缺失。

最优解决方案(无需修改SendMessage为virtual)

你需要测试的核心逻辑是返回401时会重试发送请求,完全可以通过验证可重写的SendMessageUsingHttpClient的调用次数实现测试目标,无需调整原有业务方法的修饰符。

第一步:先修复业务代码的bug

原有SendMessage递归调用后没有接收返回值,无论递归多少次都会返回第一次的401响应,修正后代码:

public HttpResponseMessage SendMessage(HttpRequestMessage httpRequestMessage, HttpStatusCode? httpStatusCode = null)
{
    GetRandomTokenAndAddInMessageHeader(httpRequestMessage, httpStatusCode);

    var response = SendMessageUsingHttpClient(httpRequestMessage);

    if (response.StatusCode == HttpStatusCode.Unauthorized)
    {
        // 接收递归调用的返回值,保证最终返回重试后的响应
        response = SendMessage(httpRequestMessage, HttpStatusCode.Unauthorized);
    }

    return response;
}

public virtual HttpResponseMessage SendMessageUsingHttpClient(HttpRequestMessage httpRequestMessage)
{
    return _httpClient.Send(httpRequestMessage, CancellationToken.None);
}

第二步:修改测试代码

[TestMethod]
public void Send_MustRetry_When_UnauthorizedReturned()
{
    var mockHttpResponseManager = new Mock<HttpRequestManager>();

    var expectedUnauthorizedResponse = new HttpResponseMessage { StatusCode = HttpStatusCode.Unauthorized };
    var expectedOkResponse = new HttpResponseMessage { StatusCode = HttpStatusCode.OK };

    mockHttpResponseManager.SetupSequence(m => m.SendMessageUsingHttpClient(It.IsAny<HttpRequestMessage>()))
        .Returns(expectedUnauthorizedResponse)
        .Returns(expectedOkResponse);

    var result = mockHttpResponseManager.Object.SendMessage(new HttpRequestMessage());

    // 验证最终返回重试后的200响应
    Assert.AreEqual(HttpStatusCode.OK, result.StatusCode);
    // 验证SendMessageUsingHttpClient被调用2次,说明触发了一次重试,完全满足测试需求
    mockHttpResponseManager.Verify(v => v.SendMessageUsingHttpClient(It.IsAny<HttpRequestMessage>()), Times.Exactly(2));
}

特殊场景方案:必须验证SendMessage调用次数

如果确实需要统计SendMessage的递归次数,可按如下方式实现:

第一步:将SendMessage改为virtual

public virtual HttpResponseMessage SendMessage(HttpRequestMessage httpRequestMessage, HttpStatusCode? httpStatusCode = null)
{
    // 同上修正后的逻辑
}

第二步:修改测试代码,开启CallBase配置

[TestMethod]
public void Send_MustCall_Twice_When_UnauthorizedReturned()
{
    // 开启CallBase,未配置的虚方法默认调用基类实现
    var mockHttpResponseManager = new Mock<HttpRequestManager>() { CallBase = true };

    var expectedUnauthorizedResponse = new HttpResponseMessage { StatusCode = HttpStatusCode.Unauthorized };
    var expectedOkResponse = new HttpResponseMessage { StatusCode = HttpStatusCode.OK };

    mockHttpResponseManager.SetupSequence(m => m.SendMessageUsingHttpClient(It.IsAny<HttpRequestMessage>()))
        .Returns(expectedUnauthorizedResponse)
        .Returns(expectedOkResponse);

    // 显式配置SendMessage调用基类实现,保证递归逻辑正常执行且被拦截统计
    mockHttpResponseManager.Setup(m => m.SendMessage(It.IsAny<HttpRequestMessage>(), It.IsAny<HttpStatusCode?>()))
        .CallBase();

    mockHttpResponseManager.Object.SendMessage(new HttpRequestMessage());

    // 此时可正确统计到2次SendMessage调用
    mockHttpResponseManager.Verify(v => v.SendMessage(It.IsAny<HttpRequestMessage>(), It.IsAny<HttpStatusCode?>()), Times.Exactly(2));
}

内容的提问来源于stack exchange,提问作者Carolynne Padilha

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 07:45:02