The magic of backend independent UI testing in Android
Have you ever asked yourself how backend independent UI testing in Android works? Yes? Then please continue reading… ;)
As an app developer, from time to time you get the chance to integrate external APIs. When it comes to our customer projects, it is often the customer’s backend from which we are able to retrieve corresponding data.
If we need to implement a new feature, the best-case scenario would be that the API has already been adapted on a staging system, so we’re able to connect to it. The reality, however, is that new features often have to be implemented by the customer, as well. So unfortunately, there is seldom an API version available that reflects the new behaviour.
There are two different approaches to start the development:
- You have a MockServer that simulates the behaviour of the real server and can simply be extended with the new function (assuming there is an agreement on how the API will be implemented).
- You mock the data within the app and return it directly during test runtime.
Both options have their advantages & disadvantages:
I am guessing that most of you are familiar with “mocking” or how MockServers work. If not, the web offers a wide variety of articles on this matter. For example, check out the following Wikipedia article on Mock objects.
Since the approach of mocked data within the app is often only described for JUnit-Tests, I would like to explain this option with regard to UI tests in this article.
Dependency Injection & MVVM
The key to all this is Dependency Injection, i.e. the insertion of defined implementations at runtime and the MVVM app architecture. We currently use Dagger to insert dependencies, but the same should apply to koin and other libraries.
The MVVM pattern describes the differentiation between model, view and view model, which makes it possible to test them.
How does it all work now?
We use a custom class that inherits from the application class and thus represents the root context of the app. In this class, Dagger is initialized, so we can inject every dependency from there to the desired places.
JUnit Tests (for comparison)
Within the JUnit tests, the desired injection can simply be overwritten (at setUp()) to provoke a desired behaviour. This way almost all test cases can be mapped.
UI Tests
Unfortunately, the procedure used for the JUnit tests does not work within the UI tests. Because within these tests no mock data (using Mockito-Kotlin) can be created, another variant for the test procedure must be used for the Dagger Dependencies.
However, since we do not have direct access to the application (only after the launch of the activity, at which time the Dagger initialization has already been performed), we must consider another construct.
1. Custom TestApplication Class for UI Tests
First, we have to define our own application class for our test runtime. This is necessary because we can’t initialize a mock as injected dependency, as in the setUp() method shown above.
2. Test runner
In order that the UI test also uses the newly created application (and not the original one), we must write our own small TestRunner. This doesn’t really contain much, but only refers to our TestApplication:
Within the build.gradle (app), we only have to use this runner for our tests:
3. Mocking Data
As described above, we can’t inject a mock, so we must do our own implementation of the interface in order to adapt it to the test runtime.
The mocked implementation could look like this:
The mocked file consists of the expectations and the actual (mock) implementation under service.
By setting an expectation, the return value can be influenced, so we can now also adjust it within the UI test for the test runtime (similar to the unit tests before).
4. TestImplementation of the Dagger initialization in the TestApplication.kt.
Now that we have a test implementation, we have to equip Dagger with it as well. We do this again in the TestApplication:
Ready for UI-Testing
After all these steps, we are finally able to write UI tests that check how the UI behaves even if there is no implementation from the backend yet.
The tests could look like this:
And that’s it! :)
Now we can write UI tests that can simulate the desired response by means of the expectation. Thus, how the UI of the app behaves in different scenarios can now be tested.
It should be said again, however, that this is now a pure UI test and not an integration test, as it is often presented.
Since no real request is sent, the network interface cannot be checked this way, only the possible use cases.
Integration tests are another topic that I will present in a subsequent article.
I hope I was able make the concept a little more understandable. You can find an example app containing this on github:
Please leave a clap if you want to see more articles like this one, thank you.