The recommended way to do HTTP calls using C# is to use HttpClient
. This class is disposable, meaning that it should be used inside a using
statement. Here is a simple example:
public class MyClass
{
public MyClass()
{
}
public void MyMethod()
{
using (var httpClient = new HttpClient());
{
HttpResponseMessage response = httpClient.GetAsync("https://ahost.com/").Result;
}
}
}
This ensures that the HttpClient
instance is properly disposed by the garbage collector once we are done with it.
However, a problem arises when the time comes to unit test MyMethod
. With the current implementation, it is impossible to mock the HTTP request. This means that the unit test would end up doing a real HTTP call, which is less than ideal.
The usual approach when it comes to mocking is to inject the dependency, instead of creating it inside the class. However, HttpClient
is almost impossible to mock out of the box. This is because it does not implement any interfaces, nor does it have virtual methods. One way to circumvent this problem is to create a wrapper class around HttpClient
. That wrapper would parrot back the HttpClient
methods that need to be mocked, where the wrapper’s methods would be virtual. Then, you inject the wrapper into MyClass
.
This approach has a few drawbacks. First, you need to create and maintain a wrapper, which in the end is unnecessary additional code. Second, since you inject the wrapper, it will probably become an internal attribute of MyClass
. By doing so, you need to make sure that you properly dispose the HttpClient
present in the wrapper.
A simpler approach does not involve a wrapper at all and it allows us to keep the using
statement above. The trick is, instead of trying to inject a HttpClient
in MyClass
, we inject a HttpClientHandler
, and pass the handler to the HttpClient
constructor. When it comes time to unit test MyClass
, we inject a FakeHttpClientHandler
. This fake handler extends HttpClientHandler
and overrides the SendAsync
method. By using this fake handler, it is possible to mock HTTP responses, capture the request messages for assertions, etc. Here is a complete example.
First, we inject HttpClientHandler
as a dependency to MyClass
.
public class MyClass
{
private HttpClientHandler _handler;
public MyClass(HttpClientHandler handler)
{
_handler = handler;
}
public void MyMethod()
{
using (var httpClient = new HttpClient(_handler, false)); // false means that the handler won't be disposed during the HttpClient's disposal
{
HttpResponseMessage response = httpClient.GetAsync("https://ahost.com/").Result;
}
}
}
One important note. If you intend to re-use the handler, like it is the case in this example, make sure to specify that the handler should not be disposed during the disposal of the HttpClient
.
Now, in the test project, we create the FakeHttpClientHandler
class. It looks like this:
public class FakeHttpClientHandler : HttpClientHandler
{
public virtual HttpResponseMessage Send(HttpRequestMessage request)
{
throw new System.NotImplementedException("Meant for mocking purposes.");
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(Send(request));
}
}
By overriding the SendAsync
method as above, any HTTP calls that go through the HttpClient
can be faked by mocking the Send
method above.
Here is a simple code sample that shows how it is then possible to mock HTTP calls using the FakeHttpClientHandler
to unit test MyClass
. The test below uses NUnit and moq.
[TestFixture]
class MyClassTest
{
private MyClass _myClass;
private Mock<FakeHttpClientHandler> _handler;
[SetUp]
public void SetUp()
{
_handler = new Mock<FakeHttpClientHandler>() { CallBase = true };
_myClass = new MyClass(_handler.Object);
}
[Test]
public void GivenASituation_WhenDoingSomething_ThenThisHappens()
{
// Given
_handler.Setup(h => h.Send(It.IsAny<HttpRequestMessage>())).Returns(new HttpResponseMessage()); // Insert the response you want
// When
_myClass.MyMethod();
// Then
// Assert something
}
}
One last important detail. It is mandatory to set CallBase = true
when creating the mocked FakeHttpClientHandler
. This is because we want SendAsync
to be called during our tests. By doing so, our mocked method (Send
) will be executed. If the CallBase
attribute is not set, the moq framework will return a default value for the SendAsync
method.
That’s it! If you have another alternative regarding mocking HttpClient
calls, please share it in the comments!