user1765862
user1765862

Reputation: 14185

mock HttpClient SendAsync method

My code is using HttpClient in order to retrieve some data

HttpClient client = new HttpClient
{
    BaseAddress = new Uri("myurl.com"),
};
var msg = new HttpRequestMessage(HttpMethod.Get, "myendpoint");
var res = await client.SendAsync(msg);

how can I mock this SendAsync method on HttpClient and inject it inside .net core ServiceCollection?

I tried to mock like this

var mockFactory = new Mock<IHttpClientFactory>();
            var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
            mockHttpMessageHandler.Protected()
                .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
                .ReturnsAsync(new HttpResponseMessage
                {
                    StatusCode = HttpStatusCode.OK,
                    Content = new StringContent("{'name':thecodebuzz,'city':'USA'}"),
                });

            var client = new HttpClient(mockHttpMessageHandler.Object);
            mockFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(client);

but how to inject this mockFactory into ServiceCollection? Or maybe there is some easier or different way around?

Upvotes: 3

Views: 3028

Answers (2)

Matt Thomas
Matt Thomas

Reputation: 5794

Instead of mocking the HTTP call, why not encapsulate it? Then you can mock the encapsulation/abstraction.

For example:

interface IClient
{
  Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default);
}

class HttpClientAdapter : IClient
{
  readonly HttpClient _client;

  public HttpClientAdapter(HttpClient client)
  {
    _client = client;
  }

  public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) => _client.SendAsync(request, cancellationToken);
}

Make your code depend on the IClient interface. During normal usage you would use a real HttpClient in the HttpClientAdapter implementation. And for tests you could mock the IClient.

Note it might be more useful to you to make the abstraction a little higher level than this. For example, if you're expecting to parse a JSON string from HTTP responses into some DataObject then maybe your IClient interface should look more like this:

class DataObject
{
  public string Name { get; set; }
  public string City { get; set; }
}

interface IClient
{
  Task<DataObject> GetAsync(CancellationToken cancellationToken = default);
}

public class ClientImplementation : IClient
{
  readonly HttpClient _client;

  public ClientImplementation(HttpClient client)
  {
    _client = client;
  }

  public async Task<DataObject> GetAsync(CancellationToken cancellationToken = default)
  {
    var response = await _client.SendAsync(...);
    var dataObject = new DataObject();
    // parse the response into the data object
    return dataObject;
  }
}

The advantage of drawing the line here is your tests will have less work to do. Your mock code won't have to set up HttpResponseMessage objects, for example.

Where you choose to draw the boundaries for your abstractions is totally up to you. But the key takeaway is: once your code depends on a small interface then it's easy to mock that interface and test your code.

Upvotes: 1

If you really need to mock the HttpClient itself, take a look at this lib: https://github.com/richardszalay/mockhttp

From the docs:

var mockHttp = new MockHttpMessageHandler();

// Setup a respond for the user api (including a wildcard in the URL)
mockHttp.When("http://localhost/api/user/*")
        .Respond("application/json", "{'name' : 'Test McGee'}"); // Respond with JSON

// Inject the handler or client into your application code
var client = mockHttp.ToHttpClient();

var response = await client.GetAsync("http://localhost/api/user/1234");
// or without async: var response = client.GetAsync("http://localhost/api/user/1234").Result;

var json = await response.Content.ReadAsStringAsync();

// No network connection required
Console.Write(json); // {'name' : 'Test McGee'}

Upvotes: 0

Related Questions