5

I have method which contains a HttpClientFactory that creates a HttpClient. The method calls SendAsync method in it. I need to manipulate SendAsync method to send me a succeed message whatever it takes as an argument.

I want to test this method

public class TestService
{
    private readonly IHttpClientFactory _clientFactory;

    public TestService(IHttpClientFactory clientFactory)
    {
         _clientFactory = clientFactory;
    }

    public async Task<bool> TestMethod(string testdata)
    {
        var message = new HttpRequestMessage(); //SIMPLIFIED CODE
        var client = _clientFactory.CreateClient();
        var response = await client.SendAsync(message);

        if(response.IsSuccessStatusCode){
            return true;
        }else{
            return false;
        }
    }
}

What I try is

private readonly Mock<IHttpClientFactory> _mockHttpClientFactory;
private readonly Mock<HttpClient> _mockHttpClient;

[Fact]
public void TestMethod_ValidHttpRequest_ReturnsTrue()
{
    var httpClient = _mockHttpClient
                        .Setup(x => x.SendAsync(It.IsAny<HttpRequestMessage>()))
                        .ReturnsAsync(new HttpResponseMessage()
                        {
                            StatusCode = HttpStatusCode.OK,
                            Content = new StringContent("[{'id':1,'value':'1'}]"),
                        });

    var client = _mockHttpClientFactory.Setup(x => x.CreateClient()).Returns(httpClient); //DOESNT WORK
    
    var service = new TestService(_mockHttpClientFactory);
    var result = service.TestMethod("testdata");
    Assert.True(result.Result)
}
Ciarán Bruen
  • 5,221
  • 13
  • 59
  • 69
Barış Velioğlu
  • 5,709
  • 15
  • 59
  • 105
  • whats your system under test here? looks like your testing is completely pointless as all the actions are on mocks, furthermore from the test you have, what part does not work that you would like us to have a look at – JohnChris Jun 17 '19 at 13:23
  • Why are you using `privateObject.Invoke` when your method under test is `public`? – Chris Jun 17 '19 at 13:25
  • Actually it contains business but I just make it simple to remove lots of code on it. – Barış Velioğlu Jun 17 '19 at 13:27
  • This may help: https://stackoverflow.com/a/54227679/2854993 – Chris Jun 17 '19 at 13:29
  • I edited the question. The problem is I cannot make mockHttpClientFactory to create a httpclient that is already mocked while it calls CreateClient() method. – Barış Velioğlu Jun 17 '19 at 13:32
  • 1
    Create a wrapper around HttpClient, inject it in your `SUT` using singleton scope, then you can use `Moq` to mock the `IHttpClient` to always return the response you want when you execute `SendAsync`. So your factory will return an `IHttpClient` – JohnChris Jun 17 '19 at 13:33
  • 1
    issue i believe is that you cant mock a class, you mock an interface, hence my solution above, to be clear, cant edit above comment, its either inject in OR use the factory you have to return the `IHttpClient`, but you would inject the factory anyway i guess – JohnChris Jun 17 '19 at 13:36
  • 1
    I totally agree with @JohnChris. I'd go with abstracting the logic into a service with an interface and injecting it into your class via constructor. See my answer to a similar question here: https://stackoverflow.com/a/56547977/2854993 – Chris Jun 17 '19 at 13:40
  • @BarışVelioğlu check out my answer – JohnChris Jun 17 '19 at 13:53
  • My recommendation would be to avoid making most classes directly dependent on `HttpClient`. Make them depend on abstractions which aren't specific to HTTP-anything. Those are easy to mock. The runtime implementation depends on an `HttpClient`. It doesn't do anything else. That class gets an integration test, not a unit test. You don't need to mock the `HttpClient`. – Scott Hannen Jun 17 '19 at 14:54
  • Lots of advice here https://stackoverflow.com/questions/54227487/how-to-mock-the-new-httpclientfactory-in-net-core-2-1-using-moq/76396646#76396646 – BeanFlicker Jun 04 '23 at 12:25

1 Answers1

5

I suggest to look into the following blog post from Gingster Ale. This example of the author shows you how you can mock calls to HttpClient:

// ARRANGE
var handlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
handlerMock
   .Protected()
   // Setup the PROTECTED method to mock
   .Setup<Task<HttpResponseMessage>>(
      "SendAsync",
      ItExpr.IsAny<HttpRequestMessage>(),
      ItExpr.IsAny<CancellationToken>()
   )
   // prepare the expected response of the mocked http call
   .ReturnsAsync(new HttpResponseMessage()
   {
      StatusCode = HttpStatusCode.OK,
      Content = new StringContent("[{'id':1,'value':'1'}]"),
   })
   .Verifiable();

// use real http client with mocked handler here
var httpClient = new HttpClient(handlerMock.Object)
{
   BaseAddress = new Uri("http://test.com/"),
};

var subjectUnderTest = new MyTestClass(httpClient);

// ACT
var result = await subjectUnderTest
   .GetSomethingRemoteAsync('api/test/whatever');

// ASSERT
result.Should().NotBeNull(); // this is fluent assertions here...
result.Id.Should().Be(1);

// also check the 'http' call was like we expected it
var expectedUri = new Uri("http://test.com/api/test/whatever");

handlerMock.Protected().Verify(
   "SendAsync",
   Times.Exactly(1), // we expected a single external request
   ItExpr.Is<HttpRequestMessage>(req =>
      req.Method == HttpMethod.Get  // we expected a GET request
      && req.RequestUri == expectedUri // to this uri
   ),
   ItExpr.IsAny<CancellationToken>()
);

Also, if you have the freedom to use something else than HttpClient, I recommend to take a look into Flurl.