Having not written any dotnet code for several years, I recently needed to write a basic web server that would receive a request, and as part of its work, it would send a request to another web server.
Being a fan of TDD, I wanted to do it in a test-driven way, which meant having to figure out how to mock the response from the other server. Searching the internet, it seems that the way to make web requests is to use an IHttpClientFactory
to create an HttpClient
. My first thought was that I would need to mock the client, but notice that HttpClient
is a concrete class, and so can't be mocked using a mocking library such as Moq. Searching for what was the right approach here, I came across this blog post, which got me most of the way there:
using Xunit;
using Microsoft.AspNetCore.Mvc;
using Moq;
using System.Net.Http;
using System.Threading.Tasks;
using Moq.Protected;
using System.Threading;
using System.Net;
using MyNamespace.Controllers;
namespace MyNamespace.Tests.Controllers
{
public class MyControllerTest
{
[Fact]
public async Task MyTest()
{
var clientFactory = new Mock<IHttpClientFactory>();
var messageHandler = new Mock<HttpMessageHandler>();
messageHandler
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>()
)
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.NoContent
})
.Verifiable();
var client = new HttpClient(messageHandler.Object);
clientFactory.Setup(_ => _.CreateClient()).Returns(client);
MyController controller =
new MyController(clientFactory.Object);
var result = await controller.ProcessRequest() as StatusCodeResult;
Assert.Equal((int)HttpStatusCode.NoContent, result.StatusCode);
}
}
}
However, the test then failed with the following error:
Error Message:
System.NotSupportedException : Unsupported expression: _ => _.CreateClient()
Extension methods (here: HttpClientFactoryExtensions.CreateClient) may not be used in setup / verification expressions.
After some head-scratching, I finally realised that the CreateClient()
method is actually an extension method so you need to mock the underlying method that that calls, which is CreatClient(string)
. Making this simple change resulted in a passing test!
Here is the updated test, with that single line changed.
[Fact]
public async Task MyTest()
{
var clientFactory = new Mock<IHttpClientFactory>();
var messageHandler = new Mock<HttpMessageHandler>();
messageHandler
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>()
)
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.NoContent
})
.Verifiable();
var client = new HttpClient(messageHandler.Object);
clientFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(client);
MyController controller =
new MyController(clientFactory.Object);
var result = await controller.ProcessRequest() as StatusCodeResult;
Assert.Equal((int)HttpStatusCode.NoContent, result.StatusCode);
}
For completeness, here is the code for the controller method that is being tested:
namespace MyNamespace.Controllers
{
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
public class MyController : Controller
{
private IHttpClientFactory clientFactory;
public MyController(IHttpClientFactory clientFactory)
{
this.clientFactory = clientFactory;
}
public IActionResult Index()
{
return Ok();
}
public async Task<IActionResult> ProcessRequest()
{
var request = new HttpRequestMessage(
HttpMethod.Get,
"http://localhost/some/path");
request.Headers.Add("Accept", "application/json");
var client = clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return NoContent();
}
else
{
return StatusCode(500);
}
}
}
}