Reputation: 1213
I am writing test cases using xUnit and Moq.
I am trying to mock PostAsync() of HttpClient, but I get an error.
Below is the code used for mocking:
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);
}
Error:
Unsupported expression: repo => repo.PostAsync(It.IsAny(), It.IsAny()) Non-overridable members (here: HttpClient.PostAsync) may not be used in setup / verification expressions.
Screenshot:
Upvotes: 40
Views: 45711
Reputation: 54
Instead of directly using an HttpClient instance in your code, use an IHttpClientFactory. In your tests, you can then create your own implementation of IHttpClientFactory that sends back a HttpClient which connects to a TestServer.
Here's an example of what your Fake Factory could look like:
public class InMemoryHttpClientFactory: IHttpClientFactory
{
private readonly TestServer _server;
public InMemoryHttpClientFactory(TestServer server)
{
_server = server;
}
public HttpClient CreateClient(string name)
{
return _server.CreateClient();
}
}
You can then setup a TestServer in your tests and have your custom IHttpClientFactory create clients for that server:
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);
[...]
}
Upvotes: 2
Reputation: 569
There's inbuilt support to apply conditions on HttpMethod and RequestUri properties of HttpRequestMessage. This way we can mock HttpGet, HttpPost and other verbs for various paths using the EndsWith method as described below.
_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")
});
Upvotes: 4
Reputation: 1825
The problem you are having indicates tight coupling, and you can resolve it by introducing an intermediate abstraction. You might want to create a class which aggregates the HttpClient
and exposes the PostAsync()
method via an interface:
// 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);
}
}
Upvotes: 2
Reputation: 131180
As other answers explain, you should mock the HttpMessageHandler or the HttpClientFactory, not HttpClient. This is such a common scenario that someone created a helper library for both cases, Moq.Contrib.HttpClient.
Copying from the General Usage
example for 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));
For 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;
});
Upvotes: 9
Reputation: 6345
Non-overridable members (here: HttpClient.PostAsync) may not be used in setup / verification expressions.
I also tried to mock the HttpClient
the same way you did, and I got the same error message.
Solution:
HttpClient
, mock the HttpMessageHandler
.Then give the mockHttpMessageHandler.Object
to your HttpClient
, which you then pass to your product code class. This works because HttpClient
uses HttpMessageHandler
under the hood:
// 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);
Note: You will also need a
using Moq.Protected;
at the top of your test file.
Then you can call your method that uses PostAsync
from your test, and PostAsync
will return an HTTP status OK response:
// Act
var returnedItem = this._iADLS_Operations.MethodThatUsesPostAsync(/*parameter(s) here*/);
Advantage:
Mocking HttpMessageHandler
means that you don't need extra classes in your product code or your test code.
Helpful resources:
Upvotes: 59