Introduction

This article explains how to mock the HttpClient using XUnit. Yes, we already have few ways to mock httpclient by writing a wrapper for HttpClient. But there is a problem for not covering test cases for HttpClient class, since we know there isn't an interface inherited with HttpClient. To handle this case, we introduced HttpClient property in the interface. Using this interface property we can achieve whatever we want around httpclient mocking.
Problem Statement
As I already mentioned, HttpClient does not inherit from any interface, so you will have to write your own. A few of them suggest a decorator-like pattern.
So, here we are mocking only wrapper class and not httpclient. So how can we mock httpclient as well? Let's see it in action, I am using Visual Studio 2019, .Net Core 2.0, and XUnit.
Step 1
To demonstrate httpclient mocking using Xunit, I am creating a simple web API application and adding a new test(XUnit) project.
Mocking Httpclient Using XUnit In .Net Core
Step 2
Let's introduce IHttpClientHelper interface to mock httpclient. Here you can see HttpClient property as well, which is used to hold the mocked HttpMessageHandler object.
Mocking Httpclient Using XUnit In .Net Core
The implementation class for IHttpClientHelper looks like below:
  1. using System;
  2. using System.Net.Http;
  3. using System.Net.Http.Headers;
  4. using System.Threading.Tasks;
  5. namespace Demo.Services
  6. {
  7. public class HttpClientHelper : IHttpClientHelper
  8. {
  9. public HttpClient HttpClient { get; set; }
  10. public async Task<TResult> GetAsync<TResult>(string requestUri)
  11. {
  12. TResult objResult = default(TResult);
  13. using (var client = this.GetHttpClient())
  14. {
  15. using (var response = await client.GetAsync(requestUri))
  16. {
  17. if (TryParse<TResult>(response, out objResult))
  18. {
  19. return objResult;
  20. }
  21. using (HttpContent content = response.Content)
  22. {
  23. throw new HttpRequestException(response.Content.ReadAsStringAsync().Result);
  24. }
  25. }
  26. }
  27. }
  28. private HttpClient GetHttpClient()
  29. {
  30. if (HttpClient == null)//While mocking we set httpclient object to bypass actual result.
  31. {
  32. var _httpClient = new HttpClient();
  33. _httpClient.DefaultRequestHeaders.Accept.Clear();
  34. _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
  35. return _httpClient;
  36. }
  37. return HttpClient;
  38. }
  39. private bool TryParse<TResult>(HttpResponseMessage response, out TResult t)
  40. {
  41. if (typeof(TResult).IsAssignableFrom(typeof(HttpResponseMessage)))
  42. {
  43. t = (TResult)Convert.ChangeType(response, typeof(TResult));
  44. return true;
  45. }
  46. if (response.IsSuccessStatusCode)
  47. {
  48. t = response.Content.ReadAsAsync<TResult>().Result;
  49. return true;
  50. }
  51. t = default(TResult);
  52. return false;
  53. }
  54. }
  55. }
Step 3
Alright, we have implemented wrapper class for HttpClient. Now let's move into Test project and write test cases for above GetAsync() method. We will see how to mock the HttpClient using the interface property.
  1. using AutoFixture;
  2. using Demo.Models;
  3. using Demo.Services;
  4. using Moq;
  5. using Moq.Protected;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Net;
  9. using System.Net.Http;
  10. using System.Net.Http.Headers;
  11. using System.Threading;
  12. using System.Threading.Tasks;
  13. using Xunit;
  14. namespace Demo.Test
  15. {
  16. public class HttpClientHelperTest
  17. {
  18. protected HttpClientHelper HttpClientHelperUnderTest { get; }
  19. public HttpClientHelperTest()
  20. {
  21. HttpClientHelperUnderTest = new HttpClientHelper();
  22. }
  23. /// <summary>
  24. /// GetAsync() test cases are resides here
  25. /// </summary>
  26. public class GetAsyncHttpHelper : HttpClientHelperTest
  27. {
  28. [Fact]
  29. public async Task When_GetAsync_Returns_Success_Result()
  30. {
  31. //Arrange;
  32. var result = new List<Weather>() { //Weather is an custom class
  33. new Weather() { Description="Test",Temp_max=1.1, Temp_min=1.1 }
  34. };
  35. var httpMessageHandler = new Mock<HttpMessageHandler>();
  36. var fixture = new Fixture();
  37. // Setup Protected method on HttpMessageHandler mock.
  38. httpMessageHandler.Protected()
  39. .Setup<Task<HttpResponseMessage>>(
  40. "SendAsync",
  41. ItExpr.IsAny<HttpRequestMessage>(),
  42. ItExpr.IsAny<CancellationToken>()
  43. )
  44. .ReturnsAsync((HttpRequestMessage request, CancellationToken token) =>
  45. {
  46. HttpResponseMessage response = new HttpResponseMessage();
  47. response.StatusCode = System.Net.HttpStatusCode.OK;//Setting statuscode
  48. response.Content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(result)); // configure your response here
  49. response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); //Setting media type for the response
  50. return response;
  51. });
  52. var httpClient = new HttpClient(httpMessageHandler.Object);
  53. httpClient.BaseAddress = fixture.Create<Uri>();
  54. HttpClientHelperUnderTest.HttpClient = httpClient; //Mocking setting Httphandler object to interface property.
  55. //Act
  56. var weatherResult = await HttpClientHelperUnderTest.GetAsync<List<Weather>>(string.Empty); //Return list of weather information for specific GET Uri.
  57. // Assert
  58. Assert.NotNull(weatherResult);
  59. }
  60. }
  61. }
  62. }
Here you can see mocking the HttpMessageHandler and assigning it to a HttpClient constructor.
SendAsync() is default implementation for all the HttpClient actions.
  1. public class HttpClient : HttpMessageInvoker
HttpMessageInvoker class looks like below.
Mocking Httpclient Using XUnit In .Net Core
Step 4
Let's quickly check the test case results:
Mocking Httpclient Using XUnit In .Net Core
Note
I have attached a sample project with this article. If you want more explanation or a live example, please download the project and refer to that.
Reference
https://dejanstojanovic.net/aspnet/2020/march/mocking-httpclient-in-unit-tests-with-moq-and-xunit/
I hope this was helpful to you. Enjoy ;)