Mocking HttpClient in unit tests
Your interface exposes the concrete HttpClient
class, therefore any classes that use this interface are tied to it, this means that it cannot be mocked.
HttpClient
does not inherit from any interface so you will have to write your own. I suggest a decorator-like pattern:
public interface IHttpHandler
{
HttpResponseMessage Get(string url);
HttpResponseMessage Post(string url, HttpContent content);
Task<HttpResponseMessage> GetAsync(string url);
Task<HttpResponseMessage> PostAsync(string url, HttpContent content);
}
And your class will look like this:
public class HttpClientHandler : IHttpHandler
{
private HttpClient _client = new HttpClient();
public HttpResponseMessage Get(string url)
{
return GetAsync(url).Result;
}
public HttpResponseMessage Post(string url, HttpContent content)
{
return PostAsync(url, content).Result;
}
public async Task<HttpResponseMessage> GetAsync(string url)
{
return await _client.GetAsync(url);
}
public async Task<HttpResponseMessage> PostAsync(string url, HttpContent content)
{
return await _client.PostAsync(url, content);
}
}
The point in all of this is that HttpClientHandler
creates its own HttpClient
, you could then of course create multiple classes that implement IHttpHandler
in different ways.
The main issue with this approach is that you are effectively writing a class that just calls methods in another class, however you could create a class that inherits from HttpClient
(See Nkosi's example, it's a much better approach than mine). Life would be much easier if HttpClient
had an interface that you could mock, unfortunately it does not.
This example is not the golden ticket however. IHttpHandler
still relies on HttpResponseMessage
, which belongs to System.Net.Http
namespace, therefore if you do need other implementations other than HttpClient
, you will have to perform some kind of mapping to convert their responses into HttpResponseMessage
objects. This of course is only a problem if you need to use multiple implementations of IHttpHandler
but it doesn't look like you do so it's not the end of the world, but it's something to think about.
Anyway, you can simply mock IHttpHandler
without having to worry about the concrete HttpClient
class as it has been abstracted away.
I recommend testing the non-async methods, as these still call the asynchronous methods but without the hassle of having to worry about unit testing asynchronous methods, see here
Mock HttpClient using Moq
That particular overload method is not virtual so is unable to be overridden by Moq.
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request);
Which is why it throws NotSupportedException
The virtual method you are looking for is this method
public virtual Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
However mocking HttpClient
is not as simple as it seems with its internal message handler.
I suggest using a concrete client with a custom message handler stub that will allow for more flexibility when faking the request.
Here is an example of a delegating handler stub.
public class DelegatingHandlerStub : DelegatingHandler {
private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
public DelegatingHandlerStub() {
_handlerFunc = (request, cancellationToken) => Task.FromResult(request.CreateResponse(HttpStatusCode.OK));
}
public DelegatingHandlerStub(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handlerFunc) {
_handlerFunc = handlerFunc;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
return _handlerFunc(request, cancellationToken);
}
}
Note the default constructor is doing basically what you were trying to mock before. It also allows for more custom scenarios with a delegate for the request.
With the stub, the test can be refactored to something like
public async Task _SendRequestAsync_Test() {
//Arrange
var handlerStub = new DelegatingHandlerStub();
var client = new HttpClient(handlerStub);
var sut = new ClassA(client);
var obj = new SomeObject() {
//Populate
};
//Act
var response = await sut.SendRequest(obj);
//Assert
Assert.IsNotNull(response);
Assert.IsTrue(response.IsSuccessStatusCode);
}
Mocking HttpClient in xunit for different endpoint
I think that you should set up two SendAsync
calls as in the following.
// response to /endpoint1
var responseMessage1 = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("test_return")
};
// response to /endpoint2
var responseMessage2 = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("test_other_return")
};
var mock = new Mock<HttpMessageHandler>();
// mock a call to /endpoint1
mock.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.Is<HttpRequestMessage>(
m => m.RequestUri.AbsolutePath.Contains(
"endpoint1")),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(responseMessage1);
// mock a call to /endpoint2
mock.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.Is<HttpRequestMessage>(
m => m.RequestUri.AbsolutePath.Contains(
"endpoint2")),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(responseMessage2);
var client = new HttpClient(mock.Object);
var agent = new Agent(client);
var resp = agent.Run();
Mocking httpclient multiple times in a single Unit Test
Sometime setting up a mock may not always be the best approach. Since HttpClient
really depends on HttpMessageHandler
you can create a simple one to handle the expected requests
class TestMessageHandler : HttpMessageHandler {
private readonly IDictionary<string, HttpResponseMessage> messages;
public TestMessageHandler(IDictionary<string, HttpResponseMessage> messages) {
this.messages = messages;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
var response = new HttpResponseMessage(HttpStatusCode.NotFound);
if (messages.ContainsKey(request.RequestUri.ToString()))
response = messages[request.RequestUri.ToString()] ?? new HttpResponseMessage(HttpStatusCode.NoContent);
response.RequestMessage = request;
return Task.FromResult(response);
}
}
The above handler just uses the URL of the incoming request to find the response in the dictionary. It can be refactored to handle more complex requests if needed, but for a simple request this will serve just fine.
Pass the handler to the client created for the test
public async Task MyTestMethod() {
//Arrange
Dictionary<string, HttpResponseMessage> messages = new Dictionary<string, HttpResponseMessage>();
messages.Add("https://somesite1.com/ping", new HttpResponseMessage() {
StatusCode = HttpStatusCode.OK,
Content = new StringContent("[{'id':'0001'," +
"'name':'Jonah'," +
"'street':'Cambridge Street 1234'}]"
)
});
messages.Add("https://somesite2.com/ping", new HttpResponseMessage() {
StatusCode = HttpStatusCode.OK,
Content = new StringContent("[{'id':'34'," +
"'insurance':'Etx'," +
"'insider':'daily'," +
"'collectives':'4'}]"
)
});
var client = new HttpClient(new TestMessageHandler(messages));
//...inject client as needed
The client with the custom handler can now be invoked as needed when verifying the expected behavior of the subject under test.
Related Topics
Accessing UI (Main) Thread Safely in Wpf
How to Convert a Gi-Normous Integer (In String Format) to Hex Format? (C#)
How to Run an Exe Program from a Windows Service Using C#
Format Excel Column to Decimal Doing Export from C#
Dot Character '.' in MVC Web API 2 for Request Such as API/People/Staff.45287
Get Last 10 Lines of Very Large Text File > 10Gb
How to Get Every Nth Item from a List<T>
Correct Use of Multimapping in Dapper
How to Add a Blend Behavior in a Style Setter
Richtextbox Syntax Highlighting in Real Time--Disabling the Repaint
Can a Byte[] Array Be Written to a File in C#
C# Object Pooling Pattern Implementation
How to Make an Event in the Usercontrol and Have It Handled in the Main Form