I want to test my httpclient post retry function in my UT, here I mock the HttpFactory:
both the first and second time, the HttpFactory always returns HttpStatusCode.InternalServerError
public class MyServiceClient
{
private readonly IHttpClientFactory _clientFactory;
public MyServiceClient(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<string> GetResponse(string test= "te")
{
using var client = _clientFactory.CreateClient("MyClient");
var content = new StringContent("{}", Encoding.UTF8, "application/json");
var response = await client.PostAsync("http://www.contoso.com/",content);
if (!response.IsSuccessStatusCode)
{
throw new ApplicationException("Application Error!");
}
var result = await response.Content.ReadAsStringAsync();
return result;
}
public async Task<string> PollyExecute()
{
try
{
var policy = Policy
.Handle<Exception>()
.WaitAndRetryAsync(3,
count => TimeSpan.FromSeconds(2),
(ex, timeSpan,retrycount, context) =>
{
Console.WriteLine(ex);
});
var response = await policy.ExecuteAsync(()=>GetResponse());
return response;
}
catch (Exception e)
{
Console.WriteLine(e);
throw ;
}
}
}
Then I use my policy to run the client postasync method, there is no issue in my first retry, I get the excepted 500 internal server error.
public class HttpClientTest
{
[Fact]
public async Task PoliceTest()
{
var messageHandler = new StubHttpMessageHandler(HttpStatusCode.InternalServerError, "Error!!!!");
var httpClient = new HttpClient(messageHandler)
{
BaseAddress = new Uri("http://mockuri")
};
var factory = Substitute.For<IHttpClientFactory>();
factory.CreateClient(Arg.Any<string>()).Returns(httpClient, httpClient);
var client = new MyServiceClient(factory);
var result = await client.PollyExecute();
}
}
public sealed class StubHttpMessageHandler : HttpMessageHandler
{
public string _response;
public HttpStatusCode _statusCode;
public StubHttpMessageHandler(HttpStatusCode statusCode, string response)
{
_statusCode = statusCode;
_response = response;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
return Task.FromResult(Send(request));
}
private HttpResponseMessage Send(HttpRequestMessage request)
{
return new HttpResponseMessage
{
Content = new StringContent(_response),
StatusCode = _statusCode,
};
}
}
However, in the second retry, when running postasync method,
It throws an exception says the httpclient is disposed.
Why? do some friends know the reason? thanks in advance!
I think you are just mocking IHttpClientFactory
incorrectly in your test setup. By design default implementation of factory returns new client (Microsoft docs):
Each call to CreateClient(String) is guaranteed to return a new HttpClient instance. Callers may cache the returned HttpClient instance indefinitely or surround its use in a using block to dispose it when desired.
In example you provided factory returns same httpClient
that gets disposed after the first use. Same disposed object gets returned by the mocked factory again and again causing disposed error.
Just modifying your example to return different instances of the client (pardon the repeated code) avoids disposed error:
...
var httpClient = new HttpClient(messageHandler)
{
BaseAddress = new Uri("http://mockuri")
};
var httpClient1 = new HttpClient(messageHandler)
{
BaseAddress = new Uri("http://mockuri")
};
var httpClient2 = new HttpClient(messageHandler)
{
BaseAddress = new Uri("http://mockuri")
};
var httpClient3 = new HttpClient(messageHandler)
{
BaseAddress = new Uri("http://mockuri")
};
var factory = Substitute.For<IHttpClientFactory>();
factory
.CreateClient(Arg.Any<string>())
.Returns(httpClient, httpClient1, httpClient2, httpClient3);
...