使用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
相关产品推荐
相关产品推荐

