A lot of mobile apps rely on data that they retrieve from a server, and this integration between app and back end is important for the overall functionality of the app. At the same time it can also be a source of headache when UI tests fail because your back end decided to have a hiccup. That’s why I prefer to create specific integration tests where the app is used against a real back end, and I try to mock the back end for other tests, such as those where I check the UI.

There are different libraries and tools available that can help you to mock an API and I plan to cover a couple of those over the coming months. In this first post about API mocking, I will show you how I created a build variant that uses locally stored data instead of making a request to an actual remote back end.

Two weeks ago I published a post about build variants, where I explained how to add build variants, and how to add tests that you can run for different build variants: https://www.testchamber.nl/test-automation/using-android-build-variants-for-testing/. That post might be a good starting point if you are not familiar with build variants. In this post we are going to revisit build variants with a different purpose: to stub an API in order to make our testing lives easier.

The goal of this post is mostly to describe the approach, and give an example of how you can use a build variant to mock an API. Your app’s architecture may be a bit different, and thus the implementation will be a little different. That’s why I don’t want to go into too much detail of the code, but I will show some examples to illustrate my approach, and my code is available on GitHub if you do want to take a closer look.

The App and the API

I will be using one of my own test apps as an example. The app (and its code) can be found on Github on my api-client branch: https://github.com/rutgurk/mailordercoffee-workshop/tree/feature/api-client.
This app has a menu, and the contents of the menu is retrieved from a remote API and displayed in a scrollable list:

The API is nothing fancy, a simple website that hosts a JSON file for me, that I retrieve by doing a GET request from the app to this url: https://www.mocky.io/v2/5d8baaad3500006200d47193 (In your case this could be a development- or test environment that your company uses, or maybe even a real production environment). Our current setup, basically looks like this:

There is an app, and that app can do a GET request to our API, and it will receive a JSON file in return. If we would take a closer look at the app, we would see that the actual situation looks like this:

The app consists out of different ‘modules’ that each have different responsibilities, and together they form the app:

  • There is an Activity that handles displaying the right things on the screen of a device, and it handles user interactions, such as clicking a button, or behaviour on a gesture.
  • There is also an APIService module that handles fetching data from an (remote) API.

Whenever my Activity wants to get data (for example on a pull down to refresh), it just asks the APIService to provide it. In a nutshell, what we want to do here, is replace the APIService with one that doesn’t retrieve the data from the actual API, but retrieves a locally stored file and returns that to the Activity.

The Code

Whenever the menu is opened in the app, the Activity loads a Fragment that handles the menu related code (displaying a list, and handling clicks on items in that list etc.). In that (Menu)Fragment the ApiService gets called whenever it needs new data. In code, the example that I described looks like this:

https://github.com/rutgurk/mailordercoffee-workshop/blob/feature/api-client/app/src/main/java/nl/testchamber/mailordercoffeeshop/order/beveragesmenu/MenuFragment.kt

The MenuFragment has a variable called ‘apiService’, that expects to be initialised with an object that confirms to the ApiService interface. I do that initialisation in the ‘onCreate’ method, where I create a new HttpApiService.

The ApiService interface looks like this:

https://github.com/rutgurk/mailordercoffee-workshop/blob/feature/api-client/apiservice/src/main/java/nl/testchamber/apiservice/interfaces/ApiService.kt

So my MenuFragment expects to be able to access an object that conforms to the ApiService interface, and thus should have two methods that it can use to get data: getBrews() and getMilkTypes(). Now, whenever my MenuFragment needs that data, it can call the getBrews method on the apiService variable:

The less you know, the better

The important part here is that my MenuFragment doesn’t really know where the data comes from, and it does not care. It just asks for the data, and it gets it. That means that it is possible to swap out the HttpApiService that fetches the data from the remote API, with one that uses local data, and the rest of my app won’t notice the difference.

To demonstrate this, I added two build variants to my app: ‘simpleDataRemote’ and ‘simpleDataLocal’. These two build variants use the same code for everything, except the implementation of a class called ‘SimpleDataProvider’. The code is structured as follows:

https://github.com/rutgurk/mailordercoffee-workshop/blob/feature/api-client/apiservice/src/simpledata/java/nl/testchamber/apiservice/HttpApiService.kt

The Activity calls the getBrews method of my HttpApiService. It waits for data and doesn’t know what is going on inside the HttpApiService. What actually happens there is that the getBrews method creates an ‘ApiRequest’ object containing the URL and request method. It then asks the SimpleDataProvider class to execute that request. For the ‘remote’ build variant the SimpleDataProvider makes an actual request to the provided URL, by using the OkHttp library:

https://github.com/rutgurk/mailordercoffee-workshop/blob/feature/api-client/apiservice/src/simpledataRemote/java/nl/testchamber/apiservice/SimpleDataProvider.kt

For the ‘local’ build variant, I swap this class out for a version that retrieves a locally stored JSON and returns that instead:

https://github.com/rutgurk/mailordercoffee-workshop/blob/feature/api-client/apiservice/src/simpledataLocal/java/nl/testchamber/apiservice/SimpleDataProvider.kt

On a high level, what we have now created looks like this:

It is now possible to run our UI tests on the ‘simpleDataLocal’ build variant and be certain that if one of the tests fails, it will not be because there is a problem with the back end. As an added bonus, it is also possible to use the locally stored JSON files to return errors or specific data states that may be hard to create using a real back end.

Things to keep in mind

The new ‘local’ build variant is perfectly fine for testing how certain kinds of data are displayed by the app, but it is important to keep in mind that there are a few crucial differences and risks that need to be covered.

  • Make sure to write integration tests for your app that run against a real back end. For example: I wrote a couple that initialise a new HttpApiService and test that it is indeed capable of retrieving a JSON and deserialising it into an object.
    Click here to see the code on Github.
  • Try to keep the code for the different build variants as close as possible, and keep your local data up to date with any changes of the real data.
    My build variants use the same code to do everything except the actual retrieval of data. That means that they also use the same code to deserialise the JSON into a data object. As a result, if the real back end data changes, my integration test will fail because the JSON cannot be deserialised into my data object (because they don’t match anymore). When I update my data object to reflect that change, my local build variant data deserialisation to that object will fail, and I will need to update my local data to match the real back end data. By using the same objects and code for deserialisation, I enforce that they are accurate.
  • Don’t forget about loading. Local data is pretty much retrieved and loaded at an instant, while a real back end usually adds some delay. This means that using local data is not necessarily a good substitute if you’d like to check if the app handles that correctly. This can be mitigated by adding an artificial delay before you return data from you local HttpApiService, but you still won’t accidentally run into any odd scenario’s.

Leave a Reply