模拟的 HttpClientHandler 上的 Moq Verify() 无法访问 Content 对象,因为它已被处置
Moq Verify() on a mocked HttpClientHandler can't access the Content object because it is disposed
我正在做一个模拟 HttpClient,所以我可以对我的代码进行单元测试。我想查看正在发布的内容。
我已经这样做了:
MockHttpMessageHandler = new Mock<FakeHttpMessageHandler>() { CallBase = true };
HttpClient = new HttpClient(MockHttpMessageHandler.Object, false);
和
MockHttpMessageHandler.Setup(c => c.Send(It.IsAny<HttpRequestMessage>())).Returns(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
});
和
MockHttpMessageHandler.Verify(c => c.Send(It.Is<HttpRequestMessage>(
m => m.Content.Headers.ContentType.MediaType == "text/xml" &&
m.Method == HttpMethod.Post &&
m.RequestUri.ToString() == "http://www.test.com/" &&
m.Content.ReadAsStringAsync().Result == "TestContent")));
除 Content
行外,这工作正常。我收到一条错误消息,指出内容已处理。
我猜这是因为它是一个流。
有没有一种优雅的方法可以测试内容?直接访问内容会很好,因为我也可以测试编码问题。
编辑:
HttpClient 被依赖注入到 class 中,它执行此操作:
public class MyHttpSenderClass
{
HttpClient _httpClient; // DI populates this
//...
public async Task<HttpResponseMessage> ComposeAndsendHttpRequestMessage(string url, string payload, string mediaType, string method)
{ var httpRequestMessage = new HttpRequestMessage(method, new Uri(url));
httpRequestMessage.Content = new StringContent(payload);
httpRequestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue(mediaType);
using (_httpClient)
{
// I want to test this httpRequestMessage is correct
var responseMessage = await _httpClient.SendAsync(httpRequestMessage);
return responseMessage;
}
}
}
异常,澄清一下:
System.ObjectDisposedException: 无法访问已释放的对象。
对象名称:'System.Net.Http.StringContent'.
这是在 m.Content.ReadAsStringAsync().Result
部分,因为内容已处理。
眼前的问题是所有调用都使用同一个响应对象。这是行不通的,因为 HttpRequestMessage 和 HttpResponseMessage 对象会尽快处理掉。如果模拟使用相同的 DbConnection、DbContext 或被测试代码处理的任何其他一次性对象,则会出现 相同 问题。
Returns
可以接受一个lambda,而不仅仅是一个对象,所以问题可以通过使用:
来解决
.Returns(reqMsg=>new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
});
或
.Returns(_ =>new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
});
我已经在其他答案的帮助下解决了这个问题,方法是读取消息处理程序本身中的内容并存储它,然后重置流。
public class FakeHttpMessageHandler : HttpMessageHandler
{
public string Content { get; set; }
public virtual HttpResponseMessage Send(HttpRequestMessage request)
{
throw new NotImplementedException("Use Moq to overrite this method");
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
MemoryStream msInput = new MemoryStream();
await request.Content.CopyToAsync(msInput);
byte[] byteInput = msInput.ToArray();
msInput.Seek(0, SeekOrigin.Begin);
Content = Encoding.UTF8.GetString(byteInput);
return Send(request);
}
}
希望这能帮助其他人在模拟 HttpClient 时尝试阅读内容。
我遇到了同样的问题,最后使用 Moq.Contrib.HttpClient 模拟了 HttpClientFactory
。使用库的扩展方法,我基本上实现了相同的目的——在内容被处理之前复制它。粘贴在这里,因为我发现这是一种更精简的方式,我们也可以做各种其他断言。
使用 SetupRequest(Predicate<HttpRequestMessage>)
扩展重载,复制 HttpRequestMessgae 内容:
public class TestClass {
// the place where we keep the content, after it gets disposed
static string _actualRequestStringContent;
private static bool MatchRequestBody(HttpRequestMessage r)
{
return _actualRequestStringContent == JsonConvert.SerializeObject(TheTestExpectedPayload);
}
public void Test()
{
// Arrange:
HttpMessageHandlerMock.SetupRequest(async request => {
// whatever the request,
// copy its body before it gets disposed, for later asserting
_actualRequestStringContent = await request.Content.ReadAsStringAsync();
return true;
})
.ReturnsAsync(() => new HttpResponseMessage() {
Content = new StringContent(JsonConvert.SerializeObject(TheTestExpectedPayload))
});
Action verifyRequestBody = () => HttpMessageHandlerMock.VerifyRequest(
MatchRequestBody, Times.Exactly(1), "Body did not match the expected body!");
// Act...
// Assert:
verifyRequestBody.Should().NotThrow("Request body should be taken properly");
}
我正在做一个模拟 HttpClient,所以我可以对我的代码进行单元测试。我想查看正在发布的内容。
我已经这样做了:
MockHttpMessageHandler = new Mock<FakeHttpMessageHandler>() { CallBase = true };
HttpClient = new HttpClient(MockHttpMessageHandler.Object, false);
和
MockHttpMessageHandler.Setup(c => c.Send(It.IsAny<HttpRequestMessage>())).Returns(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
});
和
MockHttpMessageHandler.Verify(c => c.Send(It.Is<HttpRequestMessage>(
m => m.Content.Headers.ContentType.MediaType == "text/xml" &&
m.Method == HttpMethod.Post &&
m.RequestUri.ToString() == "http://www.test.com/" &&
m.Content.ReadAsStringAsync().Result == "TestContent")));
除 Content
行外,这工作正常。我收到一条错误消息,指出内容已处理。
我猜这是因为它是一个流。
有没有一种优雅的方法可以测试内容?直接访问内容会很好,因为我也可以测试编码问题。
编辑:
HttpClient 被依赖注入到 class 中,它执行此操作:
public class MyHttpSenderClass
{
HttpClient _httpClient; // DI populates this
//...
public async Task<HttpResponseMessage> ComposeAndsendHttpRequestMessage(string url, string payload, string mediaType, string method)
{ var httpRequestMessage = new HttpRequestMessage(method, new Uri(url));
httpRequestMessage.Content = new StringContent(payload);
httpRequestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue(mediaType);
using (_httpClient)
{
// I want to test this httpRequestMessage is correct
var responseMessage = await _httpClient.SendAsync(httpRequestMessage);
return responseMessage;
}
}
}
异常,澄清一下:
System.ObjectDisposedException: 无法访问已释放的对象。 对象名称:'System.Net.Http.StringContent'.
这是在 m.Content.ReadAsStringAsync().Result
部分,因为内容已处理。
眼前的问题是所有调用都使用同一个响应对象。这是行不通的,因为 HttpRequestMessage 和 HttpResponseMessage 对象会尽快处理掉。如果模拟使用相同的 DbConnection、DbContext 或被测试代码处理的任何其他一次性对象,则会出现 相同 问题。
Returns
可以接受一个lambda,而不仅仅是一个对象,所以问题可以通过使用:
.Returns(reqMsg=>new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
});
或
.Returns(_ =>new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
});
我已经在其他答案的帮助下解决了这个问题,方法是读取消息处理程序本身中的内容并存储它,然后重置流。
public class FakeHttpMessageHandler : HttpMessageHandler
{
public string Content { get; set; }
public virtual HttpResponseMessage Send(HttpRequestMessage request)
{
throw new NotImplementedException("Use Moq to overrite this method");
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
MemoryStream msInput = new MemoryStream();
await request.Content.CopyToAsync(msInput);
byte[] byteInput = msInput.ToArray();
msInput.Seek(0, SeekOrigin.Begin);
Content = Encoding.UTF8.GetString(byteInput);
return Send(request);
}
}
希望这能帮助其他人在模拟 HttpClient 时尝试阅读内容。
我遇到了同样的问题,最后使用 Moq.Contrib.HttpClient 模拟了 HttpClientFactory
。使用库的扩展方法,我基本上实现了相同的目的——在内容被处理之前复制它。粘贴在这里,因为我发现这是一种更精简的方式,我们也可以做各种其他断言。
使用 SetupRequest(Predicate<HttpRequestMessage>)
扩展重载,复制 HttpRequestMessgae 内容:
public class TestClass {
// the place where we keep the content, after it gets disposed
static string _actualRequestStringContent;
private static bool MatchRequestBody(HttpRequestMessage r)
{
return _actualRequestStringContent == JsonConvert.SerializeObject(TheTestExpectedPayload);
}
public void Test()
{
// Arrange:
HttpMessageHandlerMock.SetupRequest(async request => {
// whatever the request,
// copy its body before it gets disposed, for later asserting
_actualRequestStringContent = await request.Content.ReadAsStringAsync();
return true;
})
.ReturnsAsync(() => new HttpResponseMessage() {
Content = new StringContent(JsonConvert.SerializeObject(TheTestExpectedPayload))
});
Action verifyRequestBody = () => HttpMessageHandlerMock.VerifyRequest(
MatchRequestBody, Times.Exactly(1), "Body did not match the expected body!");
// Act...
// Assert:
verifyRequestBody.Should().NotThrow("Request body should be taken properly");
}