Android UI testing in Compose

Misael E. Muñoz C
8 min readMay 12, 2023

--

By moving to Compose, we can leverage its powerful features to create UI tests that cover a wide range of use cases. Compose allows us to build custom UI elements in a modular and reusable manner, which greatly simplifies the process of creating and maintaining UI tests. Additionally, Compose enables us to create UI tests that are more reliable, robust, and efficient compared to traditional UI testing frameworks.

To further improve the effectiveness of our UI tests, we can employ popular testing patterns such as ObjectMother and Robot. The ObjectMother pattern facilitates the creation of test data by defining a set of preconfigured objects, while the Robot pattern simplifies the process of interacting with UI elements by encapsulating UI actions within a single object. These patterns help us write more concise, maintainable, and scalable UI tests.

Overall, the combination of Compose and testing patterns like ObjectMother and Robot allows us to develop UI tests that are more comprehensive, accurate, and efficient. By adopting this approach, we can ensure that our releases are of the highest quality and meet the expectations of our users.

Why UI Test?

UI or screen tests are crucial for ensuring the proper functioning of Compose code and enhancing the overall quality of the app by detecting and resolving bugs in the early stages of development.

Compose offers a comprehensive set of test APIs that enable developers to locate elements, verify their attributes, and simulate user actions. These testing APIs are designed to streamline the UI testing process and facilitate the creation of reliable and accurate UI tests.

Whether it’s a single composable element or an entire screen, a UI element can be referenced by a UI test. As the UI hierarchy is built, the semantic tree is created, providing a clear description of the UI structure. This enables developers to write tests that accurately reflect the intended UI behavior and ensure that the app is functioning as expected.

In summary, leveraging Compose’s built-in test APIs and semantic tree allows developers to create robust and effective UI tests that validate the functionality of their code and contribute to the overall quality of the app.

Let’s dive into some pattern concepts that I found very helpful and can help us to improve the quality of the app by covering UI tests.

ObjectMother Pattern

When testing a reasonably sized system, creating a large amount of sample data can become a time-consuming task. For example, testing a film detail may require access to related models such as recommendations or reviews, resulting in the creation of numerous objects. To simplify this process, developers can use the Object Mother pattern.

The Object Mother pattern facilitates the creation of pre-configured objects, or “object mothers”, that can generate the necessary data for automated tests. These object mothers act as factories for creating specific object configurations, simplifying the testing process and reducing the amount of repetitive code.

Using the Object Mother pattern alongside test fixtures allows developers to quickly generate the required data for their tests, resulting in faster and more efficient testing. By adopting this approach, developers can ensure that their tests accurately reflect the intended behavior of the system and identify issues early in the development process.

In summary, the Object Mother pattern is a useful tool for simplifying the creation of test data and improving the efficiency of automated testing. By using this pattern in conjunction with test fixtures, developers can streamline their testing workflow and achieve higher-quality code with less effort.

Let’s see what a mother object for movies would look like:

The first step is to create an object that holds movie-related models, that way it can be reused across multiple tests. At this point, it makes sense to have a factory object that can return standard models or models with different data types.

Robot Pattern

The Robot Pattern is a popular testing pattern in Android UI testing that aims to simplify the process of interacting with UI elements. It involves encapsulating UI actions into a single object or class, called a Robot, which serves as an abstraction layer between the UI and the test code.

The Robot Pattern improves the readability, maintainability, and scalability of UI tests by enabling developers to modularize and reuse UI actions across multiple tests. By encapsulating UI actions within a Robot, developers can easily modify and extend their tests without having to make changes to the underlying test code.

Overall, the Robot Pattern is a powerful tool for simplifying UI testing in Android apps. By using Robots, developers can create more efficient, readable, and maintainable UI tests.

Let’s see an example of this pattern oriented to the same example of movies:

The image above shows the definition of what the Screen class should hold, that class will assign all screen elements and assert functions.

One of the advantages of using the robot pattern in UI testing is that it is possible to reference the screen robot class as DSL, where it is possible to pass AndroidComposeTestRule as a parameter.

Let’s see how it should be defined:

The output will reflect what is shown in the UI test example above. All functions can be used within the DSL.

Base Configuration

Once the patterns used for the UI tests have been defined, to facilitate the implementation of the tests, some base classes have been created for the logic abstraction and configuration of the test classes, which are divided as follows:

BaseAndroidComposeTest

The purpose of this class is to provide us with the AndroidComposeRule, which is essential for tests requiring a custom activity. By configuring the initial setup for test creation, this class enables us to access the activity instance created for testing. This is particularly useful for tests where the composition content is set by the activity itself, rather than using the test rule’s setContent method. Additionally, having access to the activity instance allows us to leverage system resources, as well as other configurations.

In this class, there is an abstract variable that represents the configuration necessary for working with the states emitted from the ViewModel. To achieve this, we require an implementation of a StateFlow that is mocked to the state of the ViewModel, allowing us to change the states of the ViewModel as needed. With this configuration, we can test the ViewModel thoroughly and comprehensively.

Let’s see the definition of this class:

Although our base is a generic abstract class, which utilizes a generic type to determine the state type that our StateFlow will emit, it is crucial to note that this data type must align with the type of state that the ViewModel emits.

The Configuration interface has two functional methods — init() and setState(). The init() method enables us to establish the relationship between the ViewModel of the screen and the screen state, while the setState() method allows us to modify the current state of the ViewModel, enabling us to test the various states that the screen can exhibit.

BaseRobot

This class will allow us to encapsulate different functionalities that will be needed to carry out UI tests, such as the case of obtaining strings from the resources, among other functionalities, which could be useful to speed up the use through the Robot.

Required dependencies

The following dependencies are required for UI testing:

debugImplementation("androidx.compose.ui:ui-test-manifest:$compose_ui_version")
androidTestImplementation("androidx.compose.ui:ui-test-junit4:$compose_ui_version")

This module includes a ComposeTestRule and an Android implementation called AndroidComposeTestRule. With this rule, you can set the Compose content or access the activity.

On the other hand, we have the implementation of the mockk library which is a mock framework created for Kotlin. Like Mockito, Mockk allows you to create and add objects within test code.

Object simulation allows you to test other objects in isolation. Any dependency on the object under test can be simulated to provide fixed conditions, thus ensuring specific and stable tests.

testImplementation("io.mockk:mockk:$mock_version")
androidTestImplementation("io.mockk:mockk-android:$mock_version")

Mockk provides a more Kotlin-compatible approach to mocking. It provides a familiar API, the documentation is here.

And finally, we have the use of jUnit which is the most popular Java unit testing framework. And it is used to write and run repeatable automated tests.

testImplementation("junit:junit:$junit_version")

Continuing from the previous examples, let’s take a look at an implementation of a UI test that focuses on the details of a movie.

The MovieDetailScreenTest implementation contains two basic tests that allow demonstrating the content that the test class will have, although it can be noted that the Mockk library is used to mock the necessary dependencies that will be used to create the ViewModel instance and the lambdas necessary for it.

Then we have the implementation of the test configuration, which as mentioned above will allow us to change the states of the composable through the ViewModel, and the initialization of the ViewModel with its necessary dependencies.

Let’s see what the config looks like:

In the example, we have the init() method that is in charge of doing all the mocking of the dependencies that the ViewModel needs, such as the FetchMovieItemUC and the SaveStateHandle, as well as the initialization of the content of the test rule, which will launch the activity before start launching tests.

And finally, the declaration of the StateFlow is provided to the FetchMovieItemUC in a mocked way, so that it can be able to emit different states through the setState method.

But wait a minute, “Where is ObjectMother used?”

So, in case you wanted to test a state of the screen where the initial state has to change, in that case, the setState function has to be used and pass a new value where your composition will change the state of the screen.

Let’s dive into an example:

In the example above you can see the use of MovieObjectMother, which is being used to change the ViewModel’s state, and then we can test what we want to test with the new composition state.

Please note that this method belongs to the Robot Screen.

Conclusion

In conclusion, UI testing with Jetpack Compose can be greatly improved by utilizing the Object Mother and Robot patterns. These patterns allow for cleaner and more maintainable code, as well as a more straightforward and efficient approach to UI testing. By mocking the ViewModel and altering the state of the composition, we can effectively test the different states and scenarios that the UI may encounter, ensuring that the app meets the highest standards of quality and functionality. Overall, adopting these patterns and techniques can lead to more robust and reliable UI testing for Jetpack Compose-based applications.

All samples from this article are available in a library repository.

github.com/misaelemc/ComposeNavigation

--

--