Testing with HttpClient Interception
Tags: fsharp, csharp, dotnetcore
Published: 2019-03-25 09:16:31

---
title: Testing with HttpClient Interception
published: true
tags: fsharp,csharp,dotnetcore
canonical_url: https://stu.dev/testing-with-httpclient-interception/
---

[In my last post](https://dev.to/stuartblang/generating-a-typed-client-for-use-with-httpclientfactory-using-nswag-3e86) I showed how to automatically generate a typed client for use with `HttpClientFactory` from a swagger file. Now I want to make changes to the client's behaviour and need unit tests, in this post I will look at the [HttpClient Interception](https://github.com/justeat/httpclient-interception/) library and how we can effectively use it in our tests.

Take a look at the following code that makes use of our previously generated client.

```csharp
var pet = await petStoreClient.GetPetByIdAsync(1, ct);
var firstTag = pet.Tags.FirstOrDefault();
```

This looks fine, right? But what happens if `Tags` is `null`? Can that event happen?

The quick fix would be to use `?.` to propagate null safety, but maybe we want our deserialization to never allow collections to be null, but to instead default it to an empty collection.

To figure out the current behaviour and to document how we'd like it to work, we should write tests.

I will be writing these tests in F#, 'cos it rocks! Not only is it a really nice language, I want to use string literals containing JSON in my tests, and this is painful in C#.

## HttpClient Interception

This library is really slick, it has a fluent API that can produce an `HttpClient` loaded with an `HttpClientHandler` that will intercept requests and return configured responses, so it's great for unit testing.

The fluent API is really intuitive, you can see some great examples on the [README](https://github.com/justeat/httpclient-interception/).

```fsharp
module NSwag.PetStore.Client.Tests.Tests

open System
open Xunit
open NSwag.PetStore.Client
open JustEat.HttpClientInterception

[<Fact>]
let ``Given missing collection property, we deserialize to an empty collection. 🚀`` () =

    let builder =
        HttpRequestInterceptionBuilder()
            .Requests().ForAnyHost().ForPath("/pet/1234")
            .Responds().WithContent """{
  "id": 0,
  "name": "doggie",
  "photoUrls": [
    "string"
  ],
  "status": "available"
}"""

    let options =  HttpClientInterceptorOptions().ThrowsOnMissingRegistration()
    builder.RegisterWith options |> ignore
    use innerClient = options.CreateHttpClient()
    
    innerClient.BaseAddress <- "http://test-host" |> Uri
    
    let client = PetStoreClient(innerClient)
    let pet = client.GetPetByIdAsync(1234L) |> Async.AwaitTask |> Async.RunSynchronously
    
    Assert.NotNull pet.Tags
    Assert.Empty pet.Tags
```

Some things to call out:

- We're using `xUnit`, another great choice would be [Expecto](https://github.com/haf/expecto), however there's no test runner yet for Rider, and xUnit will work just fine here. xUnit works pretty well with F#, we don't have to have class definitions, notice how we have a module here, but as soon as we want to use `ITestOutputHelper` we have to switch to using classes.
- Function names in F# can contain pretty much anything by escaping with double-ticks: spaces, punctuation, even emojis (which means you have to use them, surely!).
- String literals can contain `"` when we use triple-quoted strings - this is great for JSON and XML snippets.

In terms of what we are doing with HttpClient Interceptor, it almost needs no explanation as it's completely declarative, the only comment I'd add is that you'll probably want the `ThrowsOnMissingRegistration` during tests, as this ensures everything is completely in-memory, and nothing passes through and materialises to an actual HTTP request .

<!--kg-card-begin: image-->

![](https://stu.dev/content/images/2019/03/image.png)<figcaption>As we suspected!</figcaption>

<!--kg-card-end: image-->

Right, so if the `Tag` part of the JSON is omitted then we end up with a null collection, which is not what we want, so let's change it.

NSwag lets us control the serialization settings in a few ways, one way is via the `jsonSerializerSettingsTransformationMethod` setting, it lets us point to a static method to configure the `JsonSerializerSettings`.

After lots of investigation, I found that this was the simplest way to achieve the deserialization that I wanted:


```csharp
using System;
using System.Collections.Generic;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace NSwag.PetStore.Client
{
    internal static class PetStoreSerializerSettings
    {
        public static JsonSerializerSettings TransformSettings(JsonSerializerSettings settings)
        {
            settings.DefaultValueHandling = DefaultValueHandling.Populate;
            settings.ContractResolver = new NullToEmptyListResolver();
            return settings;
        }
    }

    internal sealed class NullToEmptyListResolver : DefaultContractResolver
    {
        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            var property = base.CreateProperty(member, memberSerialization);

            var propType = property.PropertyType;
            if (propType.IsGenericType && 
                propType.GetGenericTypeDefinition() == typeof(ICollection<>))
                property.NullValueHandling = NullValueHandling.Include;
            return property;
        }
        
        protected override IValueProvider CreateMemberValueProvider(MemberInfo member)
        {
            var provider = base.CreateMemberValueProvider(member);

            if (member.MemberType == MemberTypes.Property)
            {
                var propType = ((PropertyInfo)member).PropertyType;
                if (propType.IsGenericType && 
                    propType.GetGenericTypeDefinition() == typeof(ICollection<>))
                {
                    return new EmptyListValueProvider(provider, propType);
                }
            }

            return provider;
        }

        class EmptyListValueProvider : IValueProvider
        {
            readonly IValueProvider innerProvider;
            readonly object defaultValue;

            public EmptyListValueProvider(IValueProvider innerProvider, Type listType)
            {
                this.innerProvider = innerProvider;
                defaultValue = Array.CreateInstance(listType.GetGenericArguments()[0], 0);
            }

            public void SetValue(object target, object value) => innerProvider.SetValue(target, value ?? defaultValue);
            public object GetValue(object target) => innerProvider.GetValue(target) ?? defaultValue;
        }
    }
}
```

If there is a simpler way (and I suspect there must be!) do let me know in the comments.

Now when we run the tests again...

<!--kg-card-begin: image-->

![](https://stu.dev/content/images/2019/03/image-1.png)<figcaption>That's better 🙂</figcaption>

<!--kg-card-end: image-->
## Other Options with HttpClient Interception

In this case I wanted my test to contain a JSON string literal, but in most cases we could instead use the `WithJsonContent` method, which takes an object and serializes it for us (optionally with serialization settings). Ironically it would have been easier in C# if I wanted to do this, as anonymous objects hasn't yet landed in F# (but it's sooo close!).

A compelling alternative would have been to use an [HTTP bundle](https://github.com/justeat/httpclient-interception#http-bundle-files) file, which to quote the docs, “can be used to store the HTTP requests to intercept and [store] their corresponding responses as JSON”.

## Closing

HttpClient Interception is the [brainchild](https://github.com/justeat/httpclient-interception/graphs/contributors) of [Martin Costello](https://twitter.com/martin_costello), who's always doing amazing things in the .NET Core community.

You can find all of the [code in this post here](https://github.com/slang25/nswag-petstore-client/tree/httpclient-interception).