How to write unit test case IHttpClientFactory

9.8k Views Asked by At

I have a method in service layer to connect to a service and i am using IHttpClientFactory. My method is working fine. Now i am trying to write unit test cases for the same.

public async Task<MyObject> MyMethodAsync(string arg1, string arg2)
{
    var client = _httpClientFactory.CreateClient("XYZ");

    var Authkey = "abc"; 
    var AuthToken = "def";
       
    var headers = new Dictionary<string, string>
        {
            { Authkey,AuthToken }
        };

    client.AddTokenToHeader(headers); //This method set  the DefaultRequestheader from the dictionary object

    var reqData = new
    {
        prop1 = "X", 
        prop2 = "Y"
    };//req object

    var content = new StringContent(JsonConvert.SerializeObject(reqData), Encoding.UTF8, "application/json");

    //This is httpClient Post call for posting data 
    HttpResponseMessage response = await client.PostAsync("postData", content); 
    if (!response.IsSuccessStatusCode || response.Content == null)
    {
        return null;
    }

    MyObject myObject = JsonConvert.DeserializeObject<MyObject>(response.Content.ReadAsStringAsync().Result);//read the result to an object
    return myObject;
}

For the above method i am writing test cases. Here i am trying to set the Post methods out put a status code OK and expecting the MyMethodAsync method to be true as the PostAsync is true. Here i am getting an exception

System.InvalidOperationException : An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set.

[Test]
public async Task MyMethodAsync_Gets_True()
{
    var response = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new StringContent("It worked!")
    };

    //Mock the httpclientfactory
    var _httpMessageHandler = new Mock<HttpMessageHandler>();
    var mockFactory = new Mock<IHttpClientFactory>(); 

    //Specify here the http method as post
    _httpMessageHandler.Protected()
      .Setup<Task<HttpResponseMessage>>("SendAsync",
       ItExpr.Is<HttpRequestMessage>(req => req.Method == HttpMethod.Post),
       ItExpr.IsAny<CancellationToken>())
       .ReturnsAsync(new HttpResponseMessage
       {
           StatusCode = HttpStatusCode.OK
       });


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

    var arg1 = "X";
    var arg2 = "D101";

    var service = new MyService(_mockAppSettings.Object, mockFactory.Object);
    var result = await service.MyMethodAsync(arg1, arg2);
    // Assert
    Assert.IsNotNull(result);
}

Can someone show what mistake i am doing here?

1

There are 1 best solutions below

1
On BEST ANSWER

As the exception says you have to

  • either call the PostAsync with an absolute url
  • or set the BaseAddress of the HttpClient

If you choose the second one all you need to do is this:

var httpClient = new HttpClient(_httpMessageHandler.Object);

httpClient.BaseAddress = new Uri("http://nonexisting.domain"); //New code

mockFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(httpClient);

With this modification the exception will be gone.
But your test will fail because the response.Content will be null and that's why MyMethodAsync will return with null.

To fix this let's change the Setup to this:

public static async Task MyMethodAsync_Gets_True()
{
    //Arrange
    MyObject resultObject = new MyObject();
    var response = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new StringContent(JsonConvert.SerializeObject(resultObject))
    };

    var _httpMessageHandler = new Mock<HttpMessageHandler>();
    _httpMessageHandler.Protected()
        .Setup<Task<HttpResponseMessage>>("SendAsync",
          ItExpr.Is<HttpRequestMessage>(req => req.Method == HttpMethod.Post),
          ItExpr.IsAny<CancellationToken>())
        .ReturnsAsync(response);
    ...

    //Act
    var result = await service.MyMethodAsync(arg1, arg2);

    //Assert
    Assert.NotNull(result);
}