API Testability – How to Structure Classes for External APIs for Maximum Testability

apiArchitectureinterfacesunit testing

I'm developing a set of classes designed to communicate with external APIs, and I'm running into trouble with how to properly structure everything for proper loose coupling and unit testing.

Currently, each API we need to talk to has a distinct class, which implements an interface a bit like this:

public interface IApiIntegration
{
    Task<string> SearchApi (List<string> searchValues);
    Task<string> GetFromApi(string idToGet);
    Task<bool> PostToApi(PostObject api);
}

Each api class inherits from a base abstract with implements this interface. That class also contains a number of helper functions which are only relevant to handling data coming to and from Apis.

Beneath the public PostToApi method of each class there are also a bunch of helper functions to build the object to be posted. These are often quite complicated, and could really do with testing. However, they're specific to the class in question and are thus private.

Inside every public function on IApiIntegration there is also, of course, a call to an external Api. For example it might look something like:

public override async Task<string> GetFromApi(string id)
{
    string result = "";
    string path = $"{integration.RootUrl}items/{id}?username={integration.Username}&key={integration.Password}";

    // client is a static instance of HttpClient
    HttpResponseMessage response = await client.GetAsync(path);
    if (response.IsSuccessStatusCode)
    {
        result = await response.Content.ReadAsStringAsync();
    }

    return result;
}

This leaves me with two problems:

1) It feels right that the helper methods in the base class and the individual classes should be open to unit testing, but also that they should be protected/private. Something, therefore, is clearly wrong with the structure.

2) It's obviously wrong to be testing external APIs so I need somehow to bypass or mock out those dependencies. But that's not possible in this structure.

How can I refactor and restructure this to ensure everything is open for unit tests?

Best Answer

... there are also a bunch of helper functions to build the object to be posted. These are often quite complicated, and could really do with testing.

Building an object, especially a complicated one, is logic which should not be hidden from the testing harness. A public function should exist that can build and return the object. It is effectively a constructor and likely should be a pure function (dependent only on input parameters and doesn't change any state.)

Similarly, processing API data is logic that should not be hidden. Again, a publicly available function that takes the kind of data you expect the API to emit and returns objects after processing the data is the best way to go.

Then your getFromAPI function will merely call the object builder to make the proper input and then call the data processor to create the correct objects. This function will end up being so simple that you shouldn't feel the need to test it (IMHO.)

The key point is that you should not feel the need to test the API functions. If you are feeling especially cautious, you might want to test that they are called by using a test double/mock, but that's about it. All you need to test is that you are creating valid object to pass into the API and that when the API returns valid data, you are processing that data correctly.

Some good videos on the subject:

Related Topic