Skip to content

Instantly share code, notes, and snippets.

@DavidPx
Last active May 24, 2019 21:05
Show Gist options
  • Save DavidPx/f0921e17e58e5ebe33d526b8430e93da to your computer and use it in GitHub Desktop.
Save DavidPx/f0921e17e58e5ebe33d526b8430e93da to your computer and use it in GitHub Desktop.
.Net Core 2.1 Testable HttpClient with Dependency Injection
public class Program : IProgram
{
public Program(/* stuff your program needs */)
{
}
private static void Main(string[] args)
{
var serviceProvider = Startup.RegisterDependencies(); // note that we do not pass in a DI modifier
var program = (IProgram)serviceProvider.GetService(typeof(IProgram));
program.Run();
}
}
public class ProgramTests
{
private Mock<HttpMessageHandler> mockHttpMessageHandler = new Mock<HttpMessageHandler>();
private void Arrange()
{
// https://gist.github.com/GeorgDangl/c0a85589616cf3ddffff054ee7cb585d
mockHttpMessageHandler
.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.Returns((HttpRequestMessage request, CancellationToken cancellationToken) =>
{
var response = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
return Task.FromResult(response);
});
}
private void Act()
{
var serviceProvider = Startup.RegisterDependencies(serviceCollection =>
{
// serviceCollection.Replace() any other services that touch the outside world..
// This mock message handler will get picked up by Startup's construction of the HTTP client factory
serviceCollection.AddSingleton(mockHttpMessageHandler.Object);
});
var program = serviceProvider.GetService<IProgram>();
program.Run();
}
}
public static class Startup
{
// Call this from Program
public static IServiceProvider RegisterDependencies(Action<IServiceCollection> additionalRegistrations = null)
{
// Register all the things your program needs..
services
.AddHttpClient<IMyService, MyService>()
// Use the DI Container's message handler.. otherwise make a new one.
// This allows the additionalRegistrations parameter to add a mocked one
// I used SocketsHttpHandler because it the default message handler in 2.1: https://docs.microsoft.com/en-us/dotnet/api/
// Your test will insert a mock HttpMessageHandler
.ConfigurePrimaryHttpMessageHandler(svcProvider => svcProvider.GetService<HttpMessageHandler>() ?? new SocketsHttpHandler());
}
}
@DavidPx
Copy link
Author

DavidPx commented May 24, 2019

This was a big pain in the butt to get working... I'm creating "integration" tests, i.e. they run all the code in Program for real, except for services which touch the outside world such as HTTP and AWS.

As you might know HttpClient is not mockable so you have to provide a mocked HttpMessageHandler. The difficulty was in figuring out how to tell the DI container to use a mock message handler, and then how to configure the behavior on the mock handler.

One (of likely many) shortcomings.. there's no way to have several different HttpMessageHandlers in the DI container if you need to have different HTTP behaviors during the same test. This workaround would work, however: https://stackoverflow.com/a/44177920/89176

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment