0

I am writing unit tests in C# using Moq framework. How can I write a mock statement for the following:

HttpClient httpClient;

var request = new HttpRequestMessage() { ... }

httpClient.Send(request);

I have found solutions for mocking SendAsync method, but not for just Send() method. Since I am writing unit tests, I do not want my test to send an actual request to the URL.

Hence, I would like to mock it and just verify if the Send method was called. What URL should be provided to ensure this behavior, without running into any other issues?

I am not in a position to modify my original code. Is it possible to use HttpMessageHandler to do this? What URL should I provide to mock this statement and how can I mock this statement without making an actual request to the URL and without running into additional issues such as invalid host or failed to connect to host etc? Thank you.

MatteoSp
  • 2,940
  • 5
  • 28
  • 36
Keer
  • 25
  • 4
  • Does it have to be a mocked object, considered using fakeiteasy? – Monofuse Jul 30 '23 at 17:20
  • Does this answer your question? [Moq setup statement for HttpClient.Send() method](https://stackoverflow.com/questions/76788663/moq-setup-statement-for-httpclient-send-method) – Peter Csala Aug 02 '23 at 19:19

2 Answers2

1

This method allows you to override a protected method. The Handler is the object which actually makes the call. If you inspect the HttpClient send and follow it down, you eventually see a call to the HttpMessageHandler.

[TestMethod]
public void Can_Send_HttpClient_Message()
{
    var mockedHttpMessageHandler = new Mock<HttpMessageHandler>();
    mockedHttpMessageHandler
        .Protected()
        .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
        .ReturnsAsync(new HttpResponseMessage(System.Net.HttpStatusCode.OK));
    mockedHttpMessageHandler
        .Protected()
        .Setup<HttpResponseMessage>("Send", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
        .Returns(new HttpResponseMessage(System.Net.HttpStatusCode.OK));

    var httpClient = new HttpClient(mockedHttpMessageHandler.Object, false);
    var request = new HttpRequestMessage(HttpMethod.Post, "https://www.test.com");
    httpClient.Send(request);
}

Mocking HttpClient in unit tests

Monofuse
  • 735
  • 6
  • 14
  • Thank you for your response. But I was looking to mock just the Send() method, not the SendAsync method. I thought I could mock the HttpMessageInvoker for this. – Keer Jul 31 '23 at 14:05
  • You don't have to setup the SendAsync, you only need to setup the methods you plan on using. – Monofuse Jul 31 '23 at 18:18
0

To make the code well extensible and testable, and also to avoid violating the SOLID principles, I propose to introduce several abstractions, which are given below. The approach uses the Decorator (wrapper) pattern and the main idea is to have an interface for the HttpClient class which is easy to inject into your code and mock.

public interface IHttpClient
{
    HttpResponseMessage Send(HttpRequestMessage request);
}

public class HttpClientWrapper : IHttpClient
{
    private readonly HttpClient _httpClient;

    public HttpClientWrapper()
    {
        _httpClient = new HttpClient();
    }

    public HttpResponseMessage Send(HttpRequestMessage request)
    {
        return _httpClient.Send(request);
    }
}

After that, in your code you use something like this, injecting the IHttpClient:

public class MyService : IMyService
{
    private readonly IHttpClient _httpClient;

    public MyService(IHttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public bool SendSomeRequest()
    {
        _httpClient.Send(new HttpRequestMessage());

        // other code goes here...
    }
}

Finally, you can mock this IHttpClient dependency as usual using the Moq framework.

Here is an example of how it could be tested (NUnit):

[TestFixture]
public class MyServiceTests
{
    private Mock<IHttpClient> _httpClientMock;

    private IMyService _service;

    public void SetUp()
    {
        _httpClientMock = new Mock<IHttpClient>();
        
        _service = new MyService(_httpClientMock.Object);
    }

    [Test]
    public void SendSomeRequest_HttpResponseIs200OK_ReturnsTrue()
    {
        // Arrange
        HttpResponseMessage message = new HttpResponseMessage()
        {
            StatusCode = HttpStatusCode.OK,
        };
        
        _httpClientMock.SetUp(m => m.Send(It.IsAny<HttpRequestMessage>())).Returns(message);
        
        // Act
        bool result = _service.SendSomeRequest();
        
        // Assert
        Assert.True(result);
    }
}
Denis Kiryanov
  • 451
  • 3
  • 9
  • Thank you for your response. But what URL should I use to mock the HTTP request? Because if I write an invalid URL or a non-existent URL, it fails to connect to Host. By writing a valid URL, I don't want to send an actual request. Will it automatically get mocked even if a valid URL is provided? – Keer Jul 31 '23 at 13:16
  • Actually, you don't need to send real requests. The main idea is to set some predefined behaviour to your abstractions. You can use `It.IsAny()` to set up your `IHttpClient.Send()` method. Please take a look at the example of the unit test , I've updated the comment. Hope this helps. – Denis Kiryanov Jul 31 '23 at 13:59
  • Thank you for your response. I am not in a position to make changes to the original code. So, I was wondering if I can directly mock the HttpMessageInvoker and use the setup statement on it directly. I still need to provide a URL so I wanted to clarify if by mocking it this way will it mock the request or will it still send an actual request?Thoughts ? – Keer Jul 31 '23 at 14:10
  • Using this [example](https://stackoverflow.com/a/76798931/5959460) you'll mock the `Send()` method and its return data. This example literally says, that the `Send()` method should return a `new HttpResponseMessage(System.Net.HttpStatusCode.OK)`, for any `HttpRequestMessage` passed to it as a parameter. – Denis Kiryanov Jul 31 '23 at 16:52