C# – How to unit test code that uses HttpClient

HttpClient is a dependency. Like any other dependency, you need to pass it into your code (aka dependency injection). By passing it in, you can mock it out in unit tests.

There are two approaches to mocking it out:

  • Wrap the HttpClient and mock out the wrapper.
  • Use a real HttpClient with a mocked out HttpMessageHandler.

In this article I’ll show examples of these two approaches.

Untested code that uses HttpClient

To get started here’s the endpoint and the untested client-side code.

Endpoint

I have an endpoint called /nflteams/getdivision. This returns a list of NFL teams belonging to the specified division. The following example shows a call to this to get the NFC North division.

nflteams/getdivisions endpoint returning the NFC North division

Untested client code

I have the following code that uses HttpClient to do a GET on the /nflteams/getdivision endpoint. This is currently untested. To show the two unit test approaches, I’ll unit test this code.

public class NFLTeamsDataService : IDisposable
{
	private readonly HttpClient HttpClient;
	private readonly UriBuilder GetDivisionsUri;

	public NFLTeamsDataService(HttpClient httpClient, string url)
	{
		HttpClient = httpClient;
		GetDivisionsUri = new UriBuilder($"{url}/nflteams/getdivision");
	}
	public async Task<List<NFLTeam>> GetDivision(string conference, string division)
	{
		GetDivisionsUri.Query = $"conference={conference}&division={division}";
		
		var response = await HttpClient.GetAsync(GetDivisionsUri.ToString());
		response.EnsureSuccessStatusCode();

		var json = await response.Content.ReadAsStringAsync();
		return JsonConvert.DeserializeObject<List<NFLTeam>>(json);
	}
	public void Dispose()
	{
		HttpClient?.Dispose();
	}
}
Code language: C# (cs)

Note: This is using Newtonsoft.Json for JSON deserialization.

Approach 1 – Wrap the HttpClient and mock the wrapper

HttpClient doesn’t implement an interface so it can’t be mocked out. Instead, I have to create a wrapper class. It’ll contain a HttpClient instance and wrap the methods I’m using.

Create a wrapper interface

public interface IHttpClientWrapper : IDisposable
{
	Task<HttpResponseMessage> GetAsync(string url);
}
Code language: C# (cs)

Implement the wrapper

public class HttpClientWrapper : IHttpClientWrapper
{
	private readonly HttpClient HttpClient;
	public HttpClientWrapper()
	{
		HttpClient = new HttpClient();
	}
	public async Task<HttpResponseMessage> GetAsync(string url)
	{
		return await HttpClient.GetAsync(url);
	}
	public void Dispose()
	{
		HttpClient?.Dispose();
	}
}
Code language: C# (cs)

Pass in the wrapper

public class NFLTeamsDataService : IDisposable
{
	private readonly IHttpClientWrapper HttpClient;
	private readonly UriBuilder GetDivisionsUri;

	public NFLTeamsDataService(IHttpClientWrapper httpClient, string url)
	{
		HttpClient = httpClient;
		GetDivisionsUri = new UriBuilder($"{url}/nflteams/getdivision");
	}
	public async Task<List<NFLTeam>> GetDivision(string conference, string division)
	{
		GetDivisionsUri.Query = $"conference={conference}&division={division}";

		var response = await HttpClient.GetAsync(GetDivisionsUri.ToString());
		response.EnsureSuccessStatusCode();

		var json = await response.Content.ReadAsStringAsync();
		return JsonConvert.DeserializeObject<List<NFLTeam>>(json);
	}
	public void Dispose()
	{
		HttpClient?.Dispose();
	}
}
Code language: C# (cs)

Add unit test – mock out the wrapper

using Moq;

[TestMethod()]
public async Task GetDivisionTest()
{
	//arrange
	var expectedTeamList = new List<NFLTeam>
	{
		new NFLTeam() { Team="Detroit Lions", Conference="NFC", Division="North"},
		new NFLTeam() { Team="Chicago Bears", Conference="NFC", Division="North"},
		new NFLTeam() { Team="Minnesota Vikings", Conference="NFC", Division="North"},
		new NFLTeam() { Team="Green Bay Packers", Conference="NFC", Division="North"},
	};
	var json = JsonConvert.SerializeObject(expectedTeamList);

	string url = "http://localhost:1234";

	HttpResponseMessage httpResponse = new HttpResponseMessage();
	httpResponse.StatusCode = System.Net.HttpStatusCode.OK;
	httpResponse.Content = new StringContent(json);

	var mockHttpClientWrapper = new Mock<IHttpClientWrapper>();
	mockHttpClientWrapper.Setup(t => t.GetAsync(It.Is<string>(s=>s.StartsWith(url))))
		.ReturnsAsync(httpResponse);


	NFLTeamsDataService service = new NFLTeamsDataService(mockHttpClientWrapper.Object, url);

	//act
	var actualTeamList = await service.GetDivision("NFC", "North");

	//assert
	CollectionAssert.AreEquivalent(expectedTeamList, actualTeamList);
}
Code language: C# (cs)

Note: I am using Moq.

Approach 2 – Pass in the real HttpClient and mock out the HttpMessageHandler

In this approach I’m passing in the actual HttpClient, but mocking out its HttpMessageHandler. This is an abstract class so it can be mocked.

No change needed to NFLTeamsDataService

I’m already passing in the HttpClient to my code, so there is no change needed.

public class NFLTeamsDataService : IDisposable
{
	private readonly HttpClient HttpClient;
	private readonly UriBuilder GetDivisionsUri;

	public NFLTeamsDataService(HttpClient httpClient, string url)
	{
		HttpClient = httpClient;
		GetDivisionsUri = new UriBuilder($"{url}/nflteams/getdivision");
	}
	public async Task<List<NFLTeam>> GetDivision(string conference, string division)
	{
		GetDivisionsUri.Query = $"conference={conference}&division={division}";
		
		var response = await HttpClient.GetAsync(GetDivisionsUri.ToString());
		response.EnsureSuccessStatusCode();

		var json = await response.Content.ReadAsStringAsync();
		return JsonConvert.DeserializeObject<List<NFLTeam>>(json);
	}
	public void Dispose()
	{
		HttpClient?.Dispose();
	}
}
Code language: C# (cs)

Add unit test – mock out HttpMessageHandler

The HttpMessageHandler class is abstract and has a protected method called SendAsync(). I want to mock out SendAsync(), so that when a GET is called on the passed in URL, it returns my HttpResponseMessage.

Because this is a protected method, I need to use a special mocking approach:

  • Call Protected().
  • Call Setup() – matching the signature of HttpResponseMessage.SendAsync(), and using a string to specify the method name.
  • Use ItExpr() instead of It() when specifying the method signature in Setup()
using Moq;

[TestMethod()]
public async Task GetDivisionTest()
{
	//arrange
	var expectedTeamList = new List<NFLTeam>
	{
		new NFLTeam() { Team="Detroit Lions", Conference="NFC", Division="North"},
		new NFLTeam() { Team="Chicago Bears", Conference="NFC", Division="North"},
		new NFLTeam() { Team="Minnesota Vikings", Conference="NFC", Division="North"},
		new NFLTeam() { Team="Green Bay Packers", Conference="NFC", Division="North"},
	};
	var json = JsonConvert.SerializeObject(expectedTeamList);

	string url = "http://localhost:1234";

	HttpResponseMessage httpResponse = new HttpResponseMessage();
	httpResponse.StatusCode = System.Net.HttpStatusCode.OK;
	httpResponse.Content = new StringContent(json, Encoding.UTF8, "application/json");

	Mock<HttpMessageHandler> mockHandler = new Mock<HttpMessageHandler>();
	mockHandler.Protected()
		.Setup<Task<HttpResponseMessage>>("SendAsync", 
		ItExpr.Is<HttpRequestMessage>(r=>r.Method == HttpMethod.Get && r.RequestUri.ToString().StartsWith(url)),
		ItExpr.IsAny<CancellationToken>())
		.ReturnsAsync(httpResponse);

	HttpClient httpClient = new HttpClient(mockHandler.Object);


	NFLTeamsDataService service = new NFLTeamsDataService(httpClient, url);

	//act
	var actualTeamList = await service.GetDivision("NFC", "North");

	//assert
	CollectionAssert.AreEquivalent(expectedTeamList, actualTeamList);
}
Code language: C# (cs)

Note: I am using Moq.

2 thoughts on “C# – How to unit test code that uses HttpClient”

  1. I am missing some code here.

    The object Mock, which is instantiated with
    var mockHttpClientWrapper = new Mock();

    Where is this?

    Also ‘It’ is missing:
    mockHttpClientWrapper.Setup(t => t.GetAsync(It.Is(s => s.StartsWith(url))))

    Can the code be downloaded? Might it be in a Git?

    this said, the article is simply showing 2 different approaches to using HttpClient in unit testing.

    I do get what is being taught but if I could run your code this would be even more fun.

    How do I get these 2 objects?

    Reply

Leave a Comment