0

this is a problem that I have only when trying to write a test. In a Net 5 solution, I test GetResult() in MyController:


public class MyController : ControllerBase
{
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly IConfiguration _config;

    public MyController(IHttpClientFactory httpClientFactory, IConfiguration config)
    {
        _httpClientFactory = httpClientFactory;
        _config = config;
    }

    public async Task<IActionResult> GetResult(int id)
    {
        var firstResult = await GetFirstResult(id);
        var secondResult = await GetSecondResult(id);

        return Ok("")
    }

    private async Task<int> GetFirstResult(int id)
    {
        using (var httpClient = _httpClientFactory.CreateClient("MySetting"))
        {
            var response = await httpClient.GetAsync($"MyUrl1{id}");
            return (response.IsSuccessStatusCode ? 0 : 1);
        }
    }

    private async Task<int> GetSecondResult(int id)
    {
        using (var httpClient = _httpClientFactory.CreateClient("MySetting"))
        {
            var response = await httpClient.GetAsync($"MyUrl2{id}");
            return (response.IsSuccessStatusCode ? 0 : 1);
        }
    }

My test:

    [Test]
    public async Task Get_Should_Return_OK_String()
    {
        var httpClientFactory = new Mock<IHttpClientFactory>();
        
        var client = new HttpClient();
        client.BaseAddress = new Uri("https://sthing/server.php");
            httpClientFactory.Setup(_ => _.CreateClient(It.IsAny<string>))).Returns(client);

        var config = InitConfiguration();
        
        var controller = new MyController(httpClientFactory.Object, config);
        var result = await controller.GetResult(1);

        Assert.NotNull(result);
    }

An exception is thrown in GetSecondResult() at the line of return(response...). Message: "Cannot access a disposed object. Object name: 'System.Net.Http.HttpClient'."

I am aware of this case Why is this HttpClient usage giving me an "Cannot access a disposed object." error? but there is no use of httpClientFactory. Is there a way to pass false to the constructor of the client through the factory? Why would it work out of the test anyway?

Ratatosk
  • 1
  • 1
  • I recommend this [article](https://anthonygiretti.com/2018/09/06/how-to-unit-test-a-class-that-consumes-an-httpclient-with-ihttpclientfactory-in-asp-net-core/) – Alexander Petrov May 10 '22 at 11:22
  • .NET 5 [goes out of support **today**](https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core). You should migrate to .NET 6, the current Long-Term-Support version. This isn't a sudden change, .NET Core's lifecycle was announced several years ago. For the most part, all you need to do is change `net5.0` to `net6.0` in your projects and update NuGet packages – Panagiotis Kanavos May 10 '22 at 11:24
  • As for the error, your own code causes it. Your test is configured to return the same HttpClient instance. `using (var httpClient...` disposes the test HttpClient. You don't have to dispose HttpClient in the first place though – Panagiotis Kanavos May 10 '22 at 11:30
  • @Alexander Petrov: yup, I used this article but here my problem is the dispose bound to using. – Ratatosk May 10 '22 at 14:41

1 Answers1

1

First of all, .NET 5 goes out of support today. You should migrate to .NET 6, the current Long-Term-Support version. This isn't a sudden change, .NET Core's lifecycle was announced several years ago. For the most part, all you need to do is change net5.0 to net6.0 in your projects and update NuGet packages.

As for the error, it's caused by the using block :

using (var httpClient = _httpClientFactory.CreateClient("MySetting"))

This isn't needed (it's actually discouraged), HttpClient instances and sockets are pooled and recycled by the HttpClientFactory.

The test code is configured to return the same HttpClient instance every time :

 var client = new HttpClient();
 client.BaseAddress = new Uri("https://sthing/server.php");
 httpClientFactory.Setup(_ => _.CreateClient(It.IsAny<string>)))
                  .Returns(client);

The call to GetFirstResult() disposes this instance and any subsequent use throws

To fix this just don't use using. HttpClient is meant to be reused anyway:

var httpClient = _httpClientFactory.CreateClient("MySetting");
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • Thank you. "t's actually discouraged": well I followed textbook Microsoft documentation, this is disappointing... – Ratatosk May 10 '22 at 14:38
  • @Ratatosk which link? I suspect there's a misunderstanding. There's no reason to dispose HttpClient instances when they're already defined as scoped objects. The scope is the ASP.NET request. [In this example from the docs](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-6.0#basic-usage) the client isn't disposed. Same with the named client example in the same page – Panagiotis Kanavos May 10 '22 at 14:43
  • ...aaand you are perfectly right, I wonder at which moment my brain decided to remember there was a dispose in Microsoft doc. Thank you for the correction, it all makes sense now. – Ratatosk May 10 '22 at 19:03