Android UI testing with mock data

Zohair Ahmed
Loblaw Digital
Published in
8 min readApr 28, 2022

For a business to excel, its product must promise quality. This means to not only exceed the expectations of the market, but continuously deliver a customer experience that redefines and increases the standards of said product. When speaking in terms of software products, quality assurance is achieved through testing.

Testing applications simplifies their maintainability and reduces the chances of defects. An especially important component to test for apps is their user interface (UI). UI tests assess the different scenarios in which a client may use an application and rely on data to determine if the correct UI is being displayed. This data can be created manually. The benefit of this is that a greater range of scenarios can be tested, but it is often avoided because of the overwhelming time it takes to create and maintain said data. The data can also be “created” automatically by retrieving data from a production server. This method drastically reduces developer labour. However, there are issues when requesting data from a production server:

  1. There are high costs of requesting data from a production server due to the greater chance of latency, resulting in wasted developer time.
  2. The results can potentially be unstable. Tests that run over a long period of time will be interrupted by system maintenance activities such as defragmentation and backups, which can lead to false negatives and flaky tests.
  3. We limit the ability to test for edge cases (such as empty/error states) and server responses (such as error 500) since production servers are meant to be as reliable as possible.

The challenge now becomes determining a solution that generates mock data for UI tests without the overexertion of developers and the costs of using production servers. Enter Raven. Raven is Loblaw Digital’s in-house Android testing library that enables developers to mock production data, store it in a shareable cloud database, and use that mock data for their UI tests. This means developers have an automated solution for creating mock data and testing their apps in a non-volatile environment.

Raven’s simplified workflow
Raven’s Workflow

Creating the Mock Data

The Internet holds an array of information hosted on servers. Apps usually need data from a server to display personalized information to its users. For example, to obtain data on a certain food item, a grocery app requests data on said food from the respective server. The app just made a network request. Typically, the request and the server communicate through REST API, which defines what the request wants the server to do (GET data on a certain food item, POST this data, etc.).

Android applications use libraries such as RetroFit and OkHttp to make these network requests. For example, if the request requests to GET data from a server, RetroFit defines where our server is through REST API, OkHttp “goes” to the server and returns with the data and RetroFit turns the data into a JSON for an app to use. This data is called a response. A response is not just the data we want, but it also has information about the request such as its status line and header.

If the request requests to POST data to a server, RetroFit turns the data we want to POST into a JSON and defines the location of the server, then OkHttp “goes” to the server and gives the data to the server to store (the act of OkHttp going to the server is called a request).

The process of OkHttp transferring data to/from the server is when Raven creates the mock data. When a GET request is made, Raven intercepts OkHttp and copies the data OkHttp receives from the server and stores it in a cloud database. Raven does the same when a POST request is made, except it copies and stores when OkHttp is going to the server with the data, as opposed to returning from the server.

Raven intercepts every REST API call, but what do we mean by intercept? Interceptors are a mechanism that performs an action whenever data is going to/coming from the server. How does an interceptor know when a request or response is made? We connect the interceptor to the OkHttp instance and act according to the REST API call (GET/POST).

RetroFit Instance Example
OkHttp Client Example
Example Endpoints for RetroFit and OkHttp Client

Storing the Mock Data

Raven stores the mock data into a cloud database because cloud databases are scalable and provide ease of access.

There are two cloud databases available: Cloud Firestore and Realtime Database — both by Firebase. How Raven copies and stores data depends on the database being used. Raven has a specific interceptor for each of the cloud databases.

Cloud Firestore

Firestore stores data via documents, which are placed in collections. Simply put, Firestore structures data as a set of hash maps where the key is the document and the value is the data (which are also HashMaps) we are looking to get.

Firestore and Raven Example

First, Raven’s Firestore interceptor mocks the data being held by OkHttp. Once the mock is created, Raven prepares to store the mock data to Firestore.

Raven will have its own collection titled “raven”. Then Raven creates a unique document for every REST API call made and stores the respective mock data as the document’s data. The IDs of these documents will be the production endpoint URL. This will allow the mock data to be stored/retrieved in an organized manner.

After the document has been created, Raven then converts the mock data into a hash map data structure so that the fields and their respective data can be stored.

Record to Firestore with Raven

Realtime Database

Realtime stores data as one large JSON object. This is favourable because it makes it easy to store and parse data.

Realtime works well with network libraries such as RetroFit, however, in operations like POST there are difficulties. Realtime creates a new JSON object with a unique ID every time, making it difficult to override already existing JSON objects.

Raven and Realtime Database Example

For Raven’s Realtime Database interceptor to store the mock data into Realtime, the first thing Raven does is define where Realtime is. The interceptor does this by taking the entire URL from RetroFit (base and endpoint URL) and changes the base URL to match the URL of the instance of Realtime Database for Raven. The interceptor will keep the endpoint the same to allow for straightforward storage and retrieval.

Raven will take the data that is being sent to/from the production server and convert into a JSON type object so that it can be easily stored into Realtime. However, Raven cannot POST the data due to the fact the Realtime creates a new ID for every POST operation.

To bypass this, Raven creates a new request to Realtime. This new request contains all the information of the original request, except changes the POST operation to a PUT operation. The POST operation is used when developers want to add data, while PUT is used when they want to update data.

Record to Realtime Database with Raven

Retrieving the Mock Data

Cloud Firestore

In a GET operation, Raven copies the response from Firebase. However, the response is a collection of documents rather than the expected REST API response. This means information such as the status code and headers will not be present in the mock data thus disabling us from testing different scenarios for our UI tests. We need the data to return as a REST API response, not a collection of objects. To solve this, Raven relies on creating a mock web server that mimics the behaviour of an actual server.

How does a mock web server benefit Raven? In Firestore’s case, the REST API responses are not straight forward enough to serialize and deserialize directly, and the data needs to be converted from one data type to another. To do this, Raven uses methods to convert the data (we’ll call these adapters). A mock web server makes it easy to test if these adapters work as intended when a network request is made. Secondly, since servers are meant to be reliable, it can be difficult to test empty/error states from a server. However, with a mock web server, developers could easily mimic such error states to ensure the app handles them gracefully. To integrate a mock web server into Raven, we used the MockWebServer library by Square.

MockWebServer creates a mock server. Raven redirects the response from the production server to the mock server by attaching a base URL interceptor to the OkHttp instance. The base URL interceptor changes the base URL RetroFit gives to OkHttp to match the location of the mock web server, which, in Raven’s case, is the localhost. This is important because now we can store the collection of objects as a REST API response. In a POST operation, MockWebServer works the same, except the request is intercepted when OkHttp is going to the production server as opposed to coming from the server.

Retrieve mock data from Firestore with Raven

Realtime Database

The data stored in Realtime is already stored as a REST API response, so there are no changes needed to be made to the response itself, the only issue is informing OkHttp where to get the response.

At first, OkHttp would go to the production server to get the data required by the UI tests. However, Raven intercepts this request and tells OkHttp to go to Realtime instead. This is once again done through the base URL interceptor. The base URL interceptor changes the base URL RetroFit gives to OkHttp to the base URL of Realtime. The endpoint URL stays the same because the data stored in the cloud database is stored the exact same way as it was in the production server. This means, after only changing the base URL, OkHttp requests the data from the Realtime rather than production, but provides the same response.

Retrieve mock data from Realtime Database with Raven

Local Files

Raven is also able to use data stored in the repo for its mock data. For example, if a JSON were to contain the data a UI test could use, developers could specify to use the locally stored mocked data as opposed to Cloud Firestore or Realtime Database. Reading from local storage is very similar to reading from Firestore. To mimic an API response, Raven relies on MockWebServer’s ability to create a local host server and sets the local mock data as the response from local host, rather than the data from Firestore, thus mimicking a production endpoint.

Recording locally is not possible due to the restrictions Android has when it comes to creating files and directories programmatically.

Retrieve mock data from repository with Raven

Conclusion

Developers are benefitting from the computerized way in which Raven generates and utilizes mock data. It instantly creates mock data at real time, removes the troubles of hitting production endpoints, and stores it in a cloud database available to both the Android and iOS teams. The solution Raven provides increases the test culture here at Loblaw Digital, and plays a key part ensuring the quality of our apps.

Raven is still at the beginning of its development at Loblaw Digital and is only expected to grow in the coming years.

--

--