How to mock HttpClient in your .NET / C# unit tests
You have a class that depends on HttpClient
(or IHttpClientFactory
for that matter), and want to unit test that. Since you want a unit test and not an integration test, it should not call a real http endpoint. You decide to mock out your dependency on HttpClient
, and soon thereafter you got stuck. After googling ‘How to mock HttpClient’ you landed on this post. So, how would you mock HttpClient
for your unit tests?
I give you the short answer right away: You don’t. You are going to mock out a dependency of the HttpClient
instead to achieve what you want. This is easier as you thought, but just not that intuitive in the first place. The long answer, and how to actually do it, is as follows.
Why did you get stuck in the first place?
Mock HttpClient - and run into problems
While trying to replace your HttpClient
with a mock of it, you will have noticed the following facts.
- First, the
HttpClient
does not have an interface which you could use for mocking. - Secondly, it doesn’t even have an abstract base class you could use.
- Even worse, the methods that you call on the
HttpClient
are not virtual, so you don’t have a chance to override them.
First (not so good) idea: Wrap the HttpClient
The next idea you could come up with is to wrap the HttpClient
in another class (aka Decorator pattern), and mock this out.
While this could technically work, your will notice that HttpClient
has a lot of methods you’d need to bridge over to the real implementation. And then you might think about how the HttpClient
gets injected in your class in the first place, as you have to swap out this injection against your wrapper type.
And then it strikes you again: You usually don’t control the HttpClient
lifetime directly. It is more likely that a HttpClientFactory
controls it, and you don’t see a way to extend it so that it instanciates your wrapper type instead.
Back to square one it is.
Working the problem
I ran into these very issues. And when something constantly gets in my way like this did, I like to think about whether it might be my way that’s problematic.
So I did some reading, looked at things and thought about the whole situation from some different point of views. It turned out that the approach to mock HttpClient
isn’t the way to go. That is also discussed in a lengthy thread on the CoreFx repo itself. This means, you really don’t mock HttpClient
. Instead, you replace the inner workings of the HttpClient
for your tests.
The solution: Replace the HttpMessageHandler within HttpClient
The HttpClient
has a constructor overload that takes an instance of the abstract class HttpMessageHandler
, and this is the class does the actual heavy lifting within the HttpClient
. This class is easy to mock too, as it only has a single method to implement: protected abstract Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
.
Using this, you can easily modify the behaviour of the HttpClient
during your tests. You create a class, derive it from HttpMessageHandler
, implement SendAsync
to return whatever HttpResponseMessage
you want the HttpClient
to return, and you are done. This implementation could be as simple as this line:
return Task.FromResult(new HttpResponseMessage() { StatusCode = StatusCode.OK, Content = new StringContent("OK") };
Using Moq to do that for you
Now, you probably don’t want to create a new class for each response. You could write a helper class for tests which you can prime with whatever response you might want, but that is probably not flexible enough.
Moq is a popular .NET library that helps mocking objects for testing. In essence it uses reflection and expressions to dynamically generate the mocks at runtime during your tests, based on specifications you declare using a fluent API.
Now, there is a slight issue here, too. As you noticed, the SendAsync
method on the abstract HttpMessageHandler
class is protected. The caveat: Moq can’t auto-implement protected methods as easy as it does with interfaces or public methods. Reason being, that the fluent API uses expressions on the mocked Type, and this can’t offer private or protected members, as you access the class from the outside here. So, we have to use some more advanced features of Moq to mock out our protected method here.
Moq therefore has an API for that. You do using Moq.Protected;
in your using clauses, and then you can go on on your Moq with the .Protected()
method. This gives you some additional methods on the Moq, where you can access the protected members using their names.
A complete test of a class using a HttpClient
using Moq would look like this:
// ARRANGE
var handlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
handlerMock
.Protected()
// Setup the PROTECTED method to mock
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>()
)
// prepare the expected response of the mocked http call
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("\[{'id':1,'value':'1'}\]"),
})
.Verifiable();
// use real http client with mocked handler here
var httpClient = new HttpClient(handlerMock.Object)
{
BaseAddress = new Uri("http://test.com/"),
};
var subjectUnderTest = new MyTestClass(httpClient);
// ACT
var result = await subjectUnderTest
.GetSomethingRemoteAsync('api/test/whatever');
// ASSERT
result.Should().NotBeNull(); // this is fluent assertions here...
result.Id.Should().Be(1);
// also check the 'http' call was like we expected it
var expectedUri = new Uri("http://test.com/api/test/whatever");
handlerMock.Protected().Verify(
"SendAsync",
Times.Exactly(1), // we expected a single external request
ItExpr.Is<HttpRequestMessage>(req =>
req.Method == HttpMethod.Get // we expected a GET request
&& req.RequestUri == expectedUri // to this uri
),
ItExpr.IsAny<CancellationToken>()
);
Conclusion
For unit tests, you don’t mock HttpClient
. Instead, you mock HttpMessageHandler
, put that into an HttpClient
, and have it return whatever you want that way. If you don’t want to create s specific derivation of HttpMessageHandler
for each test, you can also have Moq create the mocks for you automagically.