Introduction

Up until now we have been concentrating on how to setup the HttpClient Interception library to mimic http requests, setup matches and specify the test content we want. In this post we will look at how we can use the library to test our own code which relies on a HttpClient instance to make requests.

Historically writing tests against HttpClient related functionality was potentially tricky. Either you would mock out the service which interacted with the HttpClient instance so the calling code could setup expectations on the interface etc. and not have to worry about the infrastructure or you would have to dive manually into DelegatingHandlers. These two levels of abstraction both have pros and cons to them. Mocking the “level above” gives you full control in your tests but means you don’t test your code which interacts with HttpClient. Going into the world of DelegatingHandlers gives you the control of flow and allows for testing your code. It does however mean you need to understand a level of abstraction down into the framework which you may not be comfortable with.

What the HttpClient Interception library does is give the power of using DelegatingHandlers without the learning curve of having to work directly yourself. This gives you the power to specify stock responses as we’ve seen in earlier posts and then using the generated HttpClient instance we can pass this into the System Under Test, or SUT, which is ultimately what we want to test.

Basic Strongly Typed Client Setup

To demonstrate how to do this we will create a simple strongly typed client over HttpClient. To keep with the theme and familiarity this sticks with the functionality with calls to the GitHub API to request details of a user from a specified username.

public class MySimpleGithubClient 
{
    private readonly HttpClient _httpClient;

    public MySimpleGithubClient(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<GitHubUser> GetUserAsync(string name)
    {
        var response = await _httpClient.GetAsync($"/users/{name}");
        var responseAsString = await response.Content.ReadAsStringAsync();
        return System.Text.Json.JsonSerializer.Deserialize<GitHubUser>(responseAsString);
    }
}

In the example above we have a strongly typed client which takes a HttpClient in it’s constructor. We then have a GetUserAsync method which takes the required username and returns a GitHubUser instance created from the response.

The expectation of the class is the base uri for the HttpClient has already been set and this class is responsible for calling the specific path to make the request. Please note this is a simple example and does not deal with failures which would need to be addressed in a production system.

A call to the method constructs the path and makes the request. Once a request has returned the content of the response body is read and serialized into the object we expect to be returned. We want to get a test to check that this “happy path” runs as expected and then will allow for further extension of error handling etc. to be able to be added with confidence of existing processing working.

public class GitHubUser
{
    [JsonPropertyName("name")]
    public string Name { get; set; }
}

The above is the simple model class we are looking to populate and return from the strongly typed client method.

Test The Client

How do we setup the test for this strongly typed client? As with all the tests I write I like to follow the AAA, or “Arrange, Act, Assert”, syntax which allows for a clear layout of the test.

As discussed previously we need to setup the HttpClientInterceptorOptions with the HttpRequestInterceptionBuilder functionality we’ve seen before. As part of this we’re going to return an anonymous type as json to restrict the payload for testing and not use a bundle but this could also be setup with a bundle.

Once the setup for the request has been created and the HttpClient instance has been created with the base URI we can instantiate our SUT passing in the HttpClient. This can now be executed and the result returned can be checked after the result has been returned.

[Fact]
public async Task GetUser_Returns_Success()
{
    // Arrange
    var builder = new HttpRequestInterceptionBuilder()
        .ForHost("api.github.com")
        .ForPath("users/WestDiscGolf")
        .ForHttps()
        .ForMethod(HttpMethod.Get)
        .WithJsonContent(new { login = "WestDiscGolf", name = "Adam Storr" });
    var options = new HttpClientInterceptorOptions().Register(builder);
    var client = options.CreateHttpClient(new Uri("https://api.github.com/"));
    
    var sut = new MySimpleGithubClient(client);

    // Act
    var result = await sut.GetUserAsync("WestDiscGolf");

    // Assert
    result.Should().NotBeNull();
    result.Name.Should().Be("Adam Storr");
}

We can now be confident that the above code has tested our simple strongly typed client on the Happy Path and can use that as a basis for further testing.

Conclusion

In this post we have looked at using the Just Eat HttpClient Interception library to start testing our own strongly typed HttpClient based client code. This allows for a lot of power a flexibility to test code which otherwise would require a lot of processing and setup. This is the basis of further testing including error handling. It also allows for testing to work with extension libraries such as Polly to allow for configurable retries and back off strategies for connection requests.

If you’ve liked this post please check out my lightning talk about the basics of the library. In future posts I hope to continue to explore the library more so please subscribe to my rss feed (link below) and reach out to me on Twitter if you have any comments.