Search code examples
c#.netxunitnsubstitute

Why is .NetCore HttpClient disposed in second retry in my Unit Test?


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. enter image description here enter image description here

Why? do some friends know the reason? thanks in advance!


Solution

  • 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);
    ...