Last active
May 24, 2019 21:05
-
-
Save DavidPx/f0921e17e58e5ebe33d526b8430e93da to your computer and use it in GitHub Desktop.
.Net Core 2.1 Testable HttpClient with Dependency Injection
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 mockedHttpMessageHandler
. 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
HttpMessageHandler
s 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