ASP.NET Core mock HttpClient with custom HttpClientHandler - c#

I have trouble mocking with Moq.
Normally having a HttpClient I would mock it by injecting the HttpClient in the base class like this:
public class MyClass
{
private readonly HttpClient httpClient;
public MyClass(HttpClient httpClient)
{
this.httpClient = httpClient;
}
}
But now I have different functions in my class MyClass that need a custom HttpClientHandler like this:
HttpClientHandler httpClientHandler = new HttpClientHandler();
...
using var client = new HttpClient(httpClientHandler);
If I'd simply inject a HttpClient in MyClassTest with var service = new MyClass(httpMock.Object);, then the httpClient would be overwritten.
What would be the right way to test my functions and not making a real HTTP-call?

I suppose you are using typed client approach of the IHttpClientFactory. That's why your MyClass ctor receives an HttpClient instance.
If you need to mock that HttpClient then I suggest you to follow Hamid Mosalla's advice.
In short there is a helper class, which makes HttpMessageHandler's SendAsync mockable (without the need to use the Moq.Protected).
public class FakeHandler: HttpMessageHandler
{
public virtual HttpResponseMessage Send(HttpRequestMessage request)
{
throw new NotImplementedException();
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(Send(request));
}
}
You can use this helper class like this to mock any HttpClient call:
var httpResponse = new HttpResponseMessage
{
Content = new StringContent(JsonConvert.SerializeObject(responseObject))
};
var mockHandler = new Mock<FakeHandler> { CallBase = true };
mockHandler
.Setup(handler => handler.Send(It.IsAny<HttpRequestMessage>()))
.Returns(httpResponse);
var mockHttpClient = new HttpClient(mockHandler.Object);
var SUT = new MyClass(mockHttpClient);

What would be the right way to test my functions and not making a real HTTP-call?
Maybe not what you are looking for, but I suggest you consider Andrew Lock's wisdom - don't unit-test API/MVC controllers in ASP.NET Core.
For .NET Core (and .NET 5) you should avoid mocking HttpClient if you are testing a controller class.
If a controller class is not your SUT, I would wrap the HttpClient in a facade interface and mock that.

Related

What is the correct approach to pass HttpMessageHandler in HttpClient?

I want to pass HttpMessageHandler into my HttpClient. The main reason is to pass the proxy (IWebProxy).
Ideally, HttpClient is defined in constructor like this (Using DI):
public class MyApiClient : IMyApiClient
{
private readonly HttpClient _httpClient;
public MyApiClient(HttpClient httpClient)
{
// I am not sure how to pass `handler` here.
_httpClient = httpClient;
}
}
The way I am adding proxy into HttpClient is as following (Not using DI):
public class MyApiClient : IMyApiClient {
private readonly HttpClient _httpClient;
public MyApiClient() {
var proxy = new WebProxy {
Address = new Uri("http://test.com:1234"),
BypassProxyOnLocal = false,
UseDefaultCredentials = false,
};
// Create a client handler that uses the proxy
var httpClientHandler = new HttpClientHandler {
Proxy = proxy,
};
// Disable SSL verification
httpClientHandler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
_httpClient = new HttpClient(handler: httpClientHandler, disposeHandler: true);
}
}
Questions:
Is my approach correct to pass proxy?
Is it possible to pass proxy in HttpClient using DI (first approach)?
Manually handling either HttpClient or the underlying HttpMessageHandler is considered a bad practice in general due to the way the low level socket resources are managed: in most cases, it would either lead to socket starvation due to leaks, or it could lead to unusable failed connections.
Instead, you should rely on IHttpClientFactory and related abstractions. First, I'd strongly recommend reading the guidelines from Microsoft themselves here:
IHttpClientFactory with .NET
The simplest possible way to configure a single handler is to use the AddHttpClient overload. In your case, it would look something like the following:
services
.AddHttpClient<IMyApiClient, MyApiClient>((provider, client) =>
{
// Confiure one-off settings of the client here.
// There is an overload which doesn't pass the 'provider', in case
// you don't need DI during the configuration.
//
// For example (using recommended 'IOptions' approach):
var settings = provider.GetRequiredService<IOptions<MyApiSettings>>();
client.BaseAddress = settings.Value.ApiRoot;
})
.ConfigurePrimaryHttpMessageHandler(() =>
{
// Add whatever logic to create the handler here
// Similar to the other method, this also has DI-enabled overloads.
var proxy = new WebProxy
{
Address = new Uri("http://test.com:1234"),
BypassProxyOnLocal = false,
UseDefaultCredentials = false,
};
// Create a client handler that uses the proxy
var httpClientHandler = new HttpClientHandler { Proxy = proxy };
// Disable SSL verification
httpClientHandler.ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
return httpClientHandler;
});
This will automatically link the configured HttpClient instance with your MyApiClient in the container, so that you can then directly inject HttpClient in the MyApiClient constructor like in your first example:
public class MyApiClient : IMyApiClient
{
private readonly HttpClient _httpClient;
public MyApiClient(HttpClient httpClient)
{
// It will work now ;)
_httpClient = httpClient;
}
}

Mocking HttpClient when new instance is created inside the constructor

I have the following class that instantiates a new HttpClient instance in the constructor:
public class Posts
{
private readonly HttpClient _client;
public Posts(HttpClient httpClient)
{
this.httpClient = new HttpClient();
}
...
}
I've followed this blog to mock HttpClient for my test. But it's not working, since in the example, they are passing in HttpClient as a param in the constructor, whereas I am instantiating a new one every time. This results in the mock getting overwritten with what I have in the class.
The mock from the blog:
var handlerMock = new Mock<HttpMessageHandler>();
var response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(#"[{ ""id"": 1, ""title"": ""Cool post!""}, { ""id"": 100, ""title"": ""Some title""}]"),
};
handlerMock
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(response);
var httpClient = new HttpClient(handlerMock.Object);
var posts = new Posts(httpClient); // passed into constructor when the class instantiated
My questions are:
How can I stop my class from overriding the mock?
Should I put HttpClient as a param in the constructor, as shown in the blog? If so, what's the benefit of it over my way?
Your subject class is not using the mock or any client that is injected into it since the constructor initializes and tightly couples to it's own instance.
Instead the class should explicitly inject the client
For example
public class Posts {
private readonly HttpClient httpClient;
public Posts(HttpClient httpClient) {
this.httpClient = httpClient;
}
//...
}
This now allows the dependency to be changed as needed when testing, and in production code you will configure your application to inject the actual client.
Ensure that HttpCLient in registered with the DI container used by the application.
services.AddHttpClient();
so the DI container knows how to resolve it when building the object graph for dependent classes

Add an DelegationHandler (outgoing request middleware) to TestServer's HttpClient

Is it possible to add a custom DelegationHandler (outgoing request middleware) to the HttpClient built by the asp.net core TestServer?
I'm trying to combine the customization possibilities of using HttpClientFactory to influence HttpClients with the in-memory testing utility TestServer:
public class ExternalProxySslDowngradeSimulator : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken token)
{
if (request.RequestUri.Scheme == "https")
request.RequestUri = new Uri(
request.RequestUri.OriginalString.Replace("https:", "http:"));
return base.SendAsync(request, token);
}
}
[TestFixture
public class TestClass
{
[Test]
public async Task CallApi()
{
var builder = new WebHostBuilder()
.ConfigureAppConfiguration((ctx, config) => { })
.UseUrl("http://customDomain.com")
.UseStartup<CustomApi.Startup>();
var testServer = new Microsoft.AspNetCore.TestHost.TestServer(builder);
var httpClient = testServer.CreateClient();
var apiResult = await httpClient.GetAsync("https://customDomain");
}
}
I can't find a way to plug my DelegatingHandler into the HttpClient returned from testServer.CreateClient()
What I've tried so far:
Registering a custom HttpClientBuilder (did not work):
// did not work:
var builder = new WebHostBuilder()
.UseStartup<CustomApi.Startup>()
.ConfigureServices(services =>
{
services
.AddHttpClient("")
.AddHttpMessageHandler<ExternalProxySslDowngradeSimulator>();
Looking through the code for TestServer it is not hooking into that pipeline.
Custom HttpClientFactoryOptions (did not work)
Thought I could bind a custom HttpClientFactoryOptions and add a custom action to HttpClientFactoryOptions.HttpMessageHandlerBuilderActions. But the TestServer doesn't really consume this the way a default Http Client Factory does.
Customize the setup of TestServer (did not work)
Looking through the documentation / source didn't see anywhere to hook in. There's an extension method to further control DI, but I didn't see how that could get me a hook into the custom HttpMessangeHandler that TestServer is using.
So what you do is create your own client using the test server's (Server in code example) CreateHandler method and the HttpClientFactory.Create method.
var client = HttpClientFactory.Create(Server.CreateHandler(), new CustomDelegate() );
client.BaseAddress = Server.BaseAddress;
This took me forever to figure out. Hopefully this helps you.
So your example would be
var httpClient = HttpClientFactory.Create(testServer.CreateHander(), new ExternalProxySslDowngradeSimulator());
httpClient.BaseAddress = testServer.BaseAddress;

Unable to Mock HttpClient PostAsync() in unit tests

I am writing test cases using xUnit and Moq.
I am trying to mock PostAsync() of HttpClient, but I get an error.
Below is the code used for mocking:
public TestADLS_Operations()
{
var mockClient = new Mock<HttpClient>();
mockClient.Setup(repo => repo.PostAsync(It.IsAny<string>(), It.IsAny<HttpContent>())).Returns(() => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)));
this._iADLS_Operations = new ADLS_Operations(mockClient.Object);
}
Error:
Unsupported expression: repo => repo.PostAsync(It.IsAny(),
It.IsAny()) Non-overridable members (here:
HttpClient.PostAsync) may not be used in setup / verification
expressions.
Screenshot:
Non-overridable members (here: HttpClient.PostAsync) may not be used in setup / verification expressions.
I also tried to mock the HttpClient the same way you did, and I got the same error message.
Solution:
Instead of mocking the HttpClient, mock the HttpMessageHandler.
Then give the mockHttpMessageHandler.Object to your HttpClient, which you then pass to your product code class. This works because HttpClient uses HttpMessageHandler under the hood:
// Arrange
var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
mockHttpMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage { StatusCode = HttpStatusCode.OK });
var client = new HttpClient(mockHttpMessageHandler.Object);
this._iADLS_Operations = new ADLS_Operations(client);
Note: You will also need a
using Moq.Protected;
at the top of your test file.
Then you can call your method that uses PostAsync from your test, and PostAsync will return an HTTP status OK response:
// Act
var returnedItem = this._iADLS_Operations.MethodThatUsesPostAsync(/*parameter(s) here*/);
Advantage:
Mocking HttpMessageHandler means that you don't need extra classes in your product code or your test code.
Helpful resources:
Unit Testing with the HttpClient
How to mock HttpClient in your .NET / C# unit tests
As other answers explain, you should mock the HttpMessageHandler or the HttpClientFactory, not HttpClient. This is such a common scenario that someone created a helper library for both cases, Moq.Contrib.HttpClient.
Copying from the General Usage example for HttpClient :
// All requests made with HttpClient go through its handler's SendAsync() which we mock
var handler = new Mock<HttpMessageHandler>();
var client = handler.CreateClient();
// A simple example that returns 404 for any request
handler.SetupAnyRequest()
.ReturnsResponse(HttpStatusCode.NotFound);
// Match GET requests to an endpoint that returns json (defaults to 200 OK)
handler.SetupRequest(HttpMethod.Get, "https://example.com/api/stuff")
.ReturnsResponse(JsonConvert.SerializeObject(model), "application/json");
// Setting additional headers on the response using the optional configure action
handler.SetupRequest("https://example.com/api/stuff")
.ReturnsResponse(bytes, configure: response =>
{
response.Content.Headers.LastModified = new DateTime(2018, 3, 9);
})
.Verifiable(); // Naturally we can use Moq methods as well
// Verify methods are provided matching the setup helpers
handler.VerifyAnyRequest(Times.Exactly(3));
For HttpClientFactory :
var handler = new Mock<HttpMessageHandler>();
var factory = handler.CreateClientFactory();
// Named clients can be configured as well (overriding the default)
Mock.Get(factory).Setup(x => x.CreateClient("api"))
.Returns(() =>
{
var client = handler.CreateClient();
client.BaseAddress = ApiBaseUrl;
return client;
});
Visit Blog
There's inbuilt support to apply conditions on HttpMethod and RequestUri properties of HttpRequestMessage. This way we can mock HttpGet, HttpPost and other verbs for various paths using the EndsWith method as described below.
_httpMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", true,
*// Specify conditions for httpMethod and path
ItExpr.Is<HttpRequestMessage>(req => req.Method == HttpMethod.Get
&& req.RequestUri.AbsolutePath.EndsWith($"{path}"))),*
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("_0Kvpzc")
});
Instead of directly using an HttpClient instance in your code, use an IHttpClientFactory.
In your tests, you can then create your own implementation of IHttpClientFactory that sends back a HttpClient which connects to a TestServer.
Here's an example of what your Fake Factory could look like:
public class InMemoryHttpClientFactory: IHttpClientFactory
{
private readonly TestServer _server;
public InMemoryHttpClientFactory(TestServer server)
{
_server = server;
}
public HttpClient CreateClient(string name)
{
return _server.CreateClient();
}
}
You can then setup a TestServer in your tests and have your custom IHttpClientFactory create clients for that server:
public TestADLS_Operations()
{
//setup TestServer
IWebHostBuilder hostBuilder = new WebHostBuilder()
.Configure(app => app.Run(
async context =>
{
// set your response headers via the context.Response.Headers property
// set your response content like this:
byte[] content = Encoding.Unicode.GetBytes("myResponseContent");
await context.Response.Body.WriteAsync(content);
}));
var testServer = new TestServer(hostBuilder)
var factory = new InMemoryHttpClientFactory(testServer);
_iADLS_Operations = new ADLS_Operations(factory);
[...]
}
The problem you are having indicates tight coupling, and you can resolve it by introducing an intermediate abstraction. You might want to create a class which aggregates the HttpClient and exposes the PostAsync() method via an interface:
// Now you mock this interface instead, which is a pretty simple task.
// I suggest also abstracting away from an HttpResponseMessage
// This would allow you to swap for any other transport in the future. All
// of the response error handling could be done inside the message transport
// class.
public interface IMessageTransport
{
Task SendMessageAsync(string message);
}
// In ADLS_Operations ctor:
public ADLS_Operations(IMessageTransport messageTransport)
{
//...
}
public class HttpMessageTransport : IMessageTransport
{
public HttpMessageTransport()
{
this.httpClient = //get the http client somewhere.
}
public Task SendMessageAsync(string message)
{
return this.httpClient.PostAsync(message);
}
}

Mock HttpClient using Moq

I would like to unit test a class that uses HttpClient. We injected the HttpClient object in the class constructor.
public class ClassA : IClassA
{
private readonly HttpClient _httpClient;
public ClassA(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<HttpResponseMessage> SendRequest(SomeObject someObject)
{
//Do some stuff
var request = new HttpRequestMessage(HttpMethod.Post, "http://some-domain.in");
//Build the request
var response = await _httpClient.SendAsync(request);
return response;
}
}
Now we would like to unit test the ClassA.SendRequest method. We are using Ms Test for unit testing framework and Moq for mocking.
When we tried to mock the HttpClient, it throws NotSupportedException.
[TestMethod]
public async Task SendRequestAsync_Test()
{
var mockHttpClient = new Mock<HttpClient>();
mockHttpClient.Setup(
m => m.SendAsync(It.IsAny<HttpRequestMessage>()))
.Returns(() => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)));
}
How can we solve this issue?
That particular overload method is not virtual so is unable to be overridden by Moq.
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request);
Which is why it throws NotSupportedException
The virtual method you are looking for is this method
public virtual Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
However mocking HttpClient is not as simple as it seems with its internal message handler.
I suggest using a concrete client with a custom message handler stub that will allow for more flexibility when faking the request.
Here is an example of a delegating handler stub.
public class DelegatingHandlerStub : DelegatingHandler {
private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
public DelegatingHandlerStub() {
_handlerFunc = (request, cancellationToken) => Task.FromResult(request.CreateResponse(HttpStatusCode.OK));
}
public DelegatingHandlerStub(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handlerFunc) {
_handlerFunc = handlerFunc;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
return _handlerFunc(request, cancellationToken);
}
}
Note the default constructor is doing basically what you were trying to mock before. It also allows for more custom scenarios with a delegate for the request.
With the stub, the test can be refactored to something like
public async Task _SendRequestAsync_Test() {
//Arrange
var handlerStub = new DelegatingHandlerStub();
var client = new HttpClient(handlerStub);
var sut = new ClassA(client);
var obj = new SomeObject() {
//Populate
};
//Act
var response = await sut.SendRequest(obj);
//Assert
Assert.IsNotNull(response);
Assert.IsTrue(response.IsSuccessStatusCode);
}
Moq can mock out protected methods, such as SendAsync on the HttpMessageHandler that you can provide to HttpClient in its constructor.
var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
mockHttpMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK
});
var client = new HttpClient(mockHttpMessageHandler.Object);
Copied from https://www.thecodebuzz.com/unit-test-mock-httpclientfactory-moq-net-core/
Propper mocking with HttpClient is hard work as it was written before most people did unit testing in dotnet. Sometimes I setup a stub HTTP server that returns canned responses based on pattern matching the request url, meaning you test real HTTP requests not mocks but to a localhost server. Using WireMock.net makes this really easy and runs fast enough to satisfy most of my unit testing needs.
So instead of http://some-domain.in use a localhost server setup on some port, and then:
var server = FluentMockServer.Start(/*server and port can be setup here*/);
server.Given(
Request.Create()
.WithPath("/").UsingPost()
)
.RespondWith(
Response.Create()
.WithStatusCode(200)
.WithHeader("Content-Type", "application/json")
.WithBody("{'attr':'value'}")
);
You can find a more details and guidance on using wiremock in tests here.
I recently had to mock HttpClient, and I used Moq.Contrib.HttpClient. It was what I needed, and simple to use, so I thought I'd throw it out there.
Here is an example of general usage:
// All requests made with HttpClient go through its handler's SendAsync() which we mock
var handler = new Mock<HttpMessageHandler>();
var client = handler.CreateClient();
// A simple example that returns 404 for any request
handler.SetupAnyRequest()
.ReturnsResponse(HttpStatusCode.NotFound);
// Match GET requests to an endpoint that returns json (defaults to 200 OK)
handler.SetupRequest(HttpMethod.Get, "https://example.com/api/stuff")
.ReturnsResponse(JsonConvert.SerializeObject(model), "application/json");
// Setting additional headers on the response using the optional configure action
handler.SetupRequest("https://example.com/api/stuff")
.ReturnsResponse(bytes, configure: response =>
{
response.Content.Headers.LastModified = new DateTime(2018, 3, 9);
})
.Verifiable(); // Naturally we can use Moq methods as well
// Verify methods are provided matching the setup helpers
handler.VerifyAnyRequest(Times.Exactly(3));
For more info, check out author's blog post here.

Categories