.NET Core Moq单元测试同用例多次执行业务方法时如何持久化HttpClient实例
问题根因
你遇到的问题核心有两个常见可能性:
- 当前Mock配置是两次请求返回同一个HttpResponseMessage实例,第一次请求处理完成后,该响应对象会被.NET运行时自动释放,第二次读取时Content自然为null
- 若修改响应生成逻辑后仍报错,说明被测类在第一次
RunLogic执行完成后主动释放了传入的HttpClient实例,导致第二次调用时HttpClient本身已不可用
解决方案1(优先选用,无需反射)
修改你的TokenClientBuilder扩展方法,将固定返回同一个HttpResponseMessage的配置,改为每次调用SendAsync都生成新的响应实例即可解决90%以上的同类问题:
public static void TokenClientBuilder(this Mock<HttpMessageHandler> handler, string requestUrl, Func<string> function) { handler.Protected() .As<IHttpMessageHandlerMock>() .Setup(x => x.SendAsync( It.Is<HttpRequestMessage>(r => r.Method == HttpMethod.Post && r.RequestUri == new Uri(requestUrl)), It.IsAny<CancellationToken>())) // 改为每次调用都生成新的HttpResponseMessage实例,避免复用已释放的对象 .Returns(() => Task.FromResult(new HttpResponseMessage() { Content = new StringContent(function(), Encoding.UTF8, "application/json") })); }
注意保留你当前创建HttpClient时的disposeHandler=false参数,确保模拟的HttpMessageHandler不会被随HttpClient释放。
解决方案2(仅当方案1无效时使用,适配HttpClient被主动释放的场景)
如果确认被测类主动释放了构造注入的HttpClient,可以通过反射替换被测类的私有HttpClient字段,两次调用共用同一个Mock的HttpMessageHandler即可:
[Fact(DisplayName = "Http test")] public async Task Http_Test_Should_Return_Something() { var model = new Test(); model.test1= "tt"; var messageHandler = new Mock<HttpMessageHandler>(); // 用修改后的扩展方法配置Mock messageHandler.TokenClientBuilder(url, delegate () { return JsonConvert.SerializeObject(model); }); var httpClient = new HttpClient(messageHandler.Object, false); var func = new 你的被测类名(httpClient, _propertiesSetOnContruct); // 第一次执行完成私有字段赋值、常规属性收集逻辑 await func.RunLogic(); // 反射替换已被释放的私有HttpClient字段 var httpClientField = typeof(你的被测类名).GetField("被测类里存HttpClient的私有字段名", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); var newHttpClient = new HttpClient(messageHandler.Object, false); httpClientField.SetValue(func, newHttpClient); // 第二次执行读取已赋值的字典数据,完成剩余逻辑流程 await func.RunLogic(); }
私有字段名可以通过调试器查看或者反编译被测类快速获取,不需要修改被测类代码。
内容的提问来源于stack exchange,提问作者Sarrador do Beco
相关产品推荐
相关产品推荐

