Introduction

In the previous posts (here and here) we looked at the basic overview of the HttpClient Interception library, how to setup a simple Get request and enable making sure all matches required are specified. In this post we are going to explore one of the ways to create multiple uri matches with minimal code which is really useful when testing requests to chatty 3rd party web apis - this is using the concept of Bundles.

What is a Bundle?

A bundle is a json file at the end of the day. It is structured by the Just Eat Tech Http Request Bundle Schema. This allows for specifying the request matches in json form which can be consumed by the HttpClient Interception library.

More details can be found https://raw.githubusercontent.com/justeat/httpclient-interception/main/src/HttpClientInterception/Bundles/http-request-bundle-schema.json

Basic Structure

The basic structure of the bundle file is an identifier and comment section which allows for giving human readable information to the developers who will be working in the codebase. This might suggest the integration test scenario which the specific bundle is being used for. It also specifies the schema and version for compatibility purposes.

It then contains an array of Bundle Items.

What is a Bundle Item?

A bundle item is a representation of a uri match. It contains all the information which you would want to match your requests on including Http Verb, uri and request headers. It also specifies the response to return from the calling code including Status, Response Headers and the format of content (json, string or base64). In addition to this it allows the developer to specify template values to use in the definition but this is out of scope for todays post.

An example of a bundle file which has an item defined in it which represents our earlier example would be as follows.

{
  "$schema": "https://raw.githubusercontent.com/justeat/httpclient-interception/main/src/HttpClientInterception/Bundles/http-request-bundle-schema.json",
  "id": "example-http-request-bundle",
  "comment": "An example bundle of HTTP requests to be intercepted.",
  "version": 1,
  "items": [
    {
      "id": "json",
      "comment": "An HTTP request that returns JSON.",
      "uri": "https://api.github.com/users/westdiscgolf",
      "method": "GET",
      "status": "200",
      "contentFormat": "json",
      "contentJson": {
        "login": "WestDiscGolf",
        "name": "From Bundle"
      }
    }
  ]
}

As you can see from the above there is the high level properties defined and then an array with an item defined in it. This item has the specified uri for my GitHub profile as the example did before. It is specifying that it will respond to a “GET” request and respond with a 200 status code. It also specifies the format and the content of the response. In this example it is in json format. As this is inside a json file there is no need to escape any characters etc. as it is embedded in the structure.

The fact that the json payload is part of the structure allows for ease of use when setting up example payloads. You can look at documentation, swagger file or output logs (if you’re logging the request/responses while manually testing) for the requests you are testing and then paste the data directly into the bundle file. This is enabled through the use of the JToken structure from the Json.NET library.

Using the Bundle

Now the bundle is written and the request matches are setup we need to use this in code. This is done through using RegisterBundle extension method which hangs off HttpClientInterceptorOptions. Once a new instance of HttpClientInterceptorOptions has been instantiated we can pass the name of the bundle file. This needs to be the path to the bundle file in your test project. Under the hood the full text content of the file gets loaded in through the System.IO.File.ReadAllText api.

If your test project is struggling to load the bundle file make sure that the file content in the project (csproj) of the bundle file is set to content and copy if newer.

The bundle file is deserialised into a Bundle POCO which is the same shape as the json schema. Each of the hydrated BundleItems are then converted into a HttpRequestInterceptionBuilder instance and added to the options ready to use. This process is the same as if you wrote the builder code manually yourself.

Full Test Example

[Fact]
public async Task BasicBundle_Interception()
{
    // Arrange
    var options = new HttpClientInterceptorOptions().RegisterBundle("basicbundle.json");
    var client = options.CreateHttpClient();

    // Act
    var response = await client.GetStringAsync("https://api.github.com/users/westdiscgolf");

    // Assert
    response.Should().ContainAll("login", "WestDiscGolf", "name", "From Bundle");
}

If we take a look at the full test above we can see that the usage of the HttpRequestInterceptionBuilder has been removed. We have specified the bundle file name and passed it into the RegisterBundle extension method on the HttpClientInterceptorOptions instance

Once the options have been configured we can request a HttpClient instance and execute requests on it as before.

Conclusion

In this post we have walked through what a bundle file is, what a small example of a bundle file is and the different parts of it. We’ve also looked at how to use the bundle file and some sneaky information under the hood about how it gets from bundle file to options.

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.