我正在使用xUnit和Moq编写测试用例。
我正在尝试模拟HttpClient的PostAsync(),但抛出错误。
下面是用于模拟的代码。
public TestADLS_Operations()
{
var mockClient = new Mock<HttpClient>();
mockClient.Setup(repo => repo.PostAsync(It.IsAny<string>(), It.IsAny<HttpContent>())).Returns(() => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)));
this._iADLS_Operations = new ADLS_Operations(mockClient.Object);
}
错误:
不受支持的表达式:repo => repo.PostAsync(It.IsAny(), It.IsAny())不可覆盖的成员(此处: HttpClient.PostAsync)不能用于设置/验证 表达式。
屏幕截图:
答案 0 :(得分:4)
不可覆盖的成员(此处为HttpClient.PostAsync)不能在设置/验证表达式中使用。
我也尝试以与您相同的方式来模拟HttpClient
,并且得到相同的错误消息。
解决方案:
HttpClient
而不是模拟HttpMessageHandler
。然后将mockHttpMessageHandler.Object
赋予您的HttpClient
,然后将其传递给您的产品代码类。之所以有效,是因为HttpClient
在后台使用了HttpMessageHandler
:
// Arrange
var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
mockHttpMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage { StatusCode = HttpStatusCode.OK });
var client = new HttpClient(mockHttpMessageHandler.Object);
this._iADLS_Operations = new ADLS_Operations(client);
注意:您还需要一个
using Moq.Protected;
位于测试文件的顶部。
然后,您可以从测试中调用使用PostAsync
的方法,而PostAsync
将返回HTTP状态OK响应:
// Act
var returnedItem = this._iADLS_Operations.MethodThatUsesPostAsync(/*parameter(s) here*/);
优势:
模拟HttpMessageHandler
意味着您不需要产品代码或测试代码中的额外类。
有用的资源:
答案 1 :(得分:3)
正如其他答案所解释的那样,您应该模拟HttpMessageHandler或HttpClientFactory,而不是HttpClient。这是一种常见的情况,有人为两个案例Moq.Contrib.HttpClient创建了一个帮助程序库。
从General Usage
示例的HttpClient复制:
// All requests made with HttpClient go through its handler's SendAsync() which we mock
var handler = new Mock<HttpMessageHandler>();
var client = handler.CreateClient();
// A simple example that returns 404 for any request
handler.SetupAnyRequest()
.ReturnsResponse(HttpStatusCode.NotFound);
// Match GET requests to an endpoint that returns json (defaults to 200 OK)
handler.SetupRequest(HttpMethod.Get, "https://example.com/api/stuff")
.ReturnsResponse(JsonConvert.SerializeObject(model), "application/json");
// Setting additional headers on the response using the optional configure action
handler.SetupRequest("https://example.com/api/stuff")
.ReturnsResponse(bytes, configure: response =>
{
response.Content.Headers.LastModified = new DateTime(2018, 3, 9);
})
.Verifiable(); // Naturally we can use Moq methods as well
// Verify methods are provided matching the setup helpers
handler.VerifyAnyRequest(Times.Exactly(3));
对于HttpClientFactory:
var handler = new Mock<HttpMessageHandler>();
var factory = handler.CreateClientFactory();
// Named clients can be configured as well (overriding the default)
Mock.Get(factory).Setup(x => x.CreateClient("api"))
.Returns(() =>
{
var client = handler.CreateClient();
client.BaseAddress = ApiBaseUrl;
return client;
});
答案 2 :(得分:1)
使用IHttpClientFactory代替在代码中直接使用HttpClient实例。 然后,在测试中,您可以创建自己的IHttpClientFactory实现,该实现发送回连接到TestServer的HttpClient。
以下是您的假工厂外观的一个示例:
public class InMemoryHttpClientFactory: IHttpClientFactory
{
private readonly TestServer _server;
public InMemoryHttpClientFactory(TestServer server)
{
_server = server;
}
public HttpClient CreateClient(string name)
{
return _server.CreateClient();
}
}
然后,您可以在测试中设置TestServer,并让您的自定义IHttpClientFactory为该服务器创建客户端:
public TestADLS_Operations()
{
//setup TestServer
IWebHostBuilder hostBuilder = new WebHostBuilder()
.Configure(app => app.Run(
async context =>
{
// set your response headers via the context.Response.Headers property
// set your response content like this:
byte[] content = Encoding.Unicode.GetBytes("myResponseContent");
await context.Response.Body.WriteAsync(content);
}));
var testServer = new TestServer(hostBuilder)
var factory = new InMemoryHttpClientFactory(testServer);
_iADLS_Operations = new ADLS_Operations(factory);
[...]
}
答案 3 :(得分:1)
您遇到的问题表明耦合程度很高,您可以通过引入中间吸收来解决它。您可能想创建一个聚合HttpClient并通过接口方法公开PostAsync的类:
// Now you mock this interface instead, which is a pretty simple task.
// I suggest also abstracting away from an HttpResponseMessage
// This would allow you to swap for any other transport in the future. All
// of the response error handling could be done inside the message transport
// class.
public interface IMessageTransport
{
Task SendMessageAsync(string message);
}
// In ADLS_Operations ctor:
public ADLS_Operations(IMessageTransport messageTransport)
{
//...
}
public class HttpMessageTransport : IMessageTransport
{
public HttpMessageTransport()
{
this.httpClient = //get the http client somewhere.
}
public Task SendMessageAsync(string message)
{
return this.httpClient.PostAsync(message);
}
}
答案 4 :(得分:0)
内置支持在HttpRequestMessage的HttpMethod和RequestUri属性上应用条件。这样,我们可以使用EndsWith方法为各种路径模拟HttpGet,HttpPost和其他动词,如下所述。
_httpMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", true,
*// Specify conditions for httpMethod and path
ItExpr.Is<HttpRequestMessage>(req => req.Method == HttpMethod.Get
&& req.RequestUri.AbsolutePath.EndsWith($"{path}"))),*
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("_0Kvpzc")
});