I am a great fan of API tests. I do like to utilize the WebApplicationFactory
for this. I even wrote some articles about this in the past "Introduction to WebApplicationFactory". But what if we need to "mock" HttpClient
calls inside our tests?
The setup
Imagine the very minimalistic setup:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient<GreeterService>(
o => o.BaseAddress = new Uri("https://steven-giesel.com"));
var app = builder.Build();
app.MapGet("/", async (GreeterService service) => await service.GetSomethingAsync());
app.Run();
public partial class Program;
public sealed class GreeterService
{
private readonly HttpClient _client;
public GreeterService(HttpClient client) => _client = client;
public async Task<string> GetSomethingAsync() => await _client.GetStringAsync("/api");
}
We are utilizing a HttpClient
inside our GreeterService
. Now this class isn't really mockable. How should we approach that?
Because for sure we don't want to make real HTTP requests in our tests (at least to external systems) as they make the test unstable and flaky!
The test
So let's write a very basic test with the WebApplicationFactory
:
public class ApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public ApiTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
}
[Fact]
public async Task ShouldGreet()
{
using var client = _factory.CreateClient();
var response = await client.GetStringAsync("/");
Assert.NotNull(response);
}
}
Now this test works just fine and will probably pass (well if my website isn't down). But we want to bring everything under our control to make that test:
- Stable
- Able to return fake data
Meet:
HttpMessageHandler
/ DelegatingHandler
DelegatingHandler
is a "middleware" for your HttpClient
. So everytime you call SendAsync
that handler will be invoked. Normally you would use it to do some logging or do authentication stuff and what not. But we can also use it in testing scenarios!
The handler for our test will look like this (I will put the link to the code at the end of the blog post):
public class FakeDataHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.RequestUri is { AbsoluteUri: "https://steven-giesel.com/api" })
{
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("Hello World"),
});
}
return base.SendAsync(request, cancellationToken);
}
}
Now we only have to use the Handler:
public ApiTests(WebApplicationFactory<Program> factory)
{
_factory = factory.WithWebHostBuilder(s =>
s.ConfigureTestServices(sc =>
{
sc.AddTransient<FakeDataHandler>();
sc.ConfigureHttpClientDefaults(d =>
d.AddHttpMessageHandler<FakeDataHandler>());
}));
}
And if we build a test like this, it will pass:
[Fact]
public async Task ShouldGreet()
{
using var client = _factory.CreateClient();
var response = await client.GetStringAsync("/");
Assert.NotNull(response);
Assert.Equal("Hello World", response);
}
Hurray! We did it. A super convenient way of mocking your Http requests via the HttpClient
. And you can easily add extensions here:
- Instead of hardcoding the data, you could also get it directly from the DI container. Where you have a mapping between an expected
Uri
and the response as string or JSON object. - You could also throw an exception, if the route isn't hit. So fail fast! Also bonus points: As you don't make unnecessary requests to a service!