Using External API's in a Testable Way with C#

Welcome

In this post I will show how to write unit tests for your code when you need to call an external API. I, like usual, will be using the Internet Chuck Norris Database API from http://www.icndb.com/api/ for my example. This example covers using Moq, a .Net Nuget package for creating Mock Objects for your unit tests.


The Problem

If you are calling out to an external API in your code, you may run into the risk where the Unit tests for your code rely on an API that someone else built being up.

This would be terrible because now your unit tests could be slow and even worse be inconsistent.


Setting Up

I will be using the Refit and Refit.HttpClientFactory Nuget packages to call the external API, but the principles will apply either way.

Here is the setup for Refit if you're interested. First, we make our model classes, then we make an interface to represent the API call.

public class Joke
{
    public string Type { get; set; }
    public JokeContents Value { get; set; }
}

public class JokeContents
{
    public int Id { get; set; }
    public string Joke { get; set; }
}

public interface IChuckNorrisApi
{
    [Get("/jokes/random/")]
    Task<Joke> GetRandomJokeAsync();
}

The start of the Solution

We need to abstract our API call behind a service because the API call itself isn’t something we can test. But we want to be able to Mock it in our tests.

First, we need to create an interface that we will be able to create a mock object of later.

public interface IExternalAPIService
{
    Task<Joke> GetJokeAsync();
}

Now we can create an implementation of the interface

public class ExternalAPIService
{
    public async Task<Joke> GetJokeAsync()
    {
        var chuckNorrisApi = RestService.For<IChuckNorrisApi>("http://api.icndb.com");
        return await chuckNorrisApi.GetRandomJokeAsync();
    }
}

Because we want our program to be reliable even if the Chuck Norris database is down, we can do something like this.

public class ExternalAPIService : IExternalAPIService
{
    public async Task<Joke> GetJokeAsync()
    {
        try
        {
            var chuckNorrisApi = RestService.For<IChuckNorrisApi>("http://api.icndb.com");
            return await chuckNorrisApi.GetRandomJokeAsync();
        }
        catch
        {
            return new Joke { Type = "Failure", Value = new JokeContents { Id = -1, Joke = "Chuck Norris has counted to infinity. Twice." } };
        }
    }
}

You can define what you want it to return whenever it fails to be whatever you want. In this case we are making sure we always get a Joke even if an exception is thrown.


Our Application

The Chuck Norris API by default returns “ ” as &quot &quot (you can add ?escape=javascript with this API to make it return quotes as normal, but let’s pretend you can’t for the sake of this post).

In our application we’ll want to fix the quotes before returning the joke to the user.

Now let’s create an outline for our own service

public class FormattedJokeService
{
    public async Task<string> GetFormattedJokeAsync()
    {
        throw new NotImplementedException();
    }
}

We will also need to add a constructor to this class to Inject the ExternalAPIService

public FormattedJokeService(IExternalAPIService externalAPIService)
{
    _externalAPIService = externalAPIService;
}

Writing our Unit Test

In a test project we will create a unit test for this function.

Here’s the setup.

private Mock<IExternalAPIService> mockExternalApiService;
private Joke jokeWithQuotes;

[SetUp]
public void Setup()
{
    mockExternalApiService = new Mock<IExternalAPIService>();
    jokeWithQuotes = new Joke { Value = new JokeContents { Joke = "&quotThis is a test&quot" } };
}

And the test for our function.

[Test]
public async Task GetFormattedJokeFixesQuotes()
{
    mockExternalApiService.Setup(eas => eas.GetJokeAsync()).ReturnsAsync(jokeWithQuotes);
    var formattedJokeService = new FormattedJokeService(mockExternalApiService.Object);

    var expected = "\"This is a test\"";
    var actual = await formattedJokeService.GetFormattedJokeAsync();

    Assert.AreEqual(expected, actual);
}

Implementing the Code

Now that we have our test, we can write the function

public async Task<string> GetFormattedJokeAsync()
{
    var joke = await _externalAPIService.GetJokeAsync();
    return joke.Value.Joke.Replace("&quot","\"");
}

And there we have it. We have a tested application that doesn’t rely on the external API.


Closing Thoughts

It is easy to consume external API’s in our applications and still write our code in such a way that it is testable and that our tests are consistent.