Setting up Koin for Android UI testing

Setting up Koin for Android UI testing

·

3 min read

Problem definition

Recently I've come to a problem running my UI tests because of a DefinitionOverrideException. That's an exception thrown by the Koin library when we have a duplicated definition for a particular type. The way we use Koin on Android is by starting the container in the onCreate() method of the Application class and loading the relevant modules into the container. Here is how it may look like:

The problem is that the default test runner uses this Application class to launch the app for the UI tests. It means that on the application startup, those modules will get loaded in the Koin container. However, in our tests we want to be flexible with the loaded modules - don't load the production modules and load only modules that are relevant for the current test cases. Most of the time we want to use classes different than the real production classes called Test-Doubles (Mocks/Stubs/etc.), to avoid making real network calls, or real database manipulation, and to control the output for a given input. That helps us make the tests reliable and fast, but more on that topic in another blog post. Now, normally we would like to load a specific module per particular test case. Here is an example test setup:

So, since the module in this test contains a definition for a type that was already loaded within the application startup, the Koin library throws that DefinitionOverrideException, meaning that we cannot override the definition that was already loaded.

Solution

The way to solve this problem is for the tests to use an Application class different than the default Application class. So, we can define a testing Application class in the UI testing folder, because it will be relevant only for the UI tests. Here is an example of how it may look like:

An empty container

In the onCreate() method of the TestRoboApp class we are starting an empty Koin container, and we are stopping it in the onTerminate() method. When running the tests, for each test case we have an empty container where we can load relevant modules that we need, and unload them when we are done. We can do so by using the @Before and @After annotated methods:

A custom test runner

Next, in order to use the newly created testing Application class instead of the default one, we have to define custom test runner and configure it to use the class we want. We can create this custom test runner inside the UI testing folder as well:

All we have to do to configure this custom test runner is to override its newApplication() method and use the class name of our testing Application class.

Replace the default test runner

Ultimately, in our application's build.gradle file we need to replace the default test runner

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

with our custom test runner:

testInstrumentationRunner = "nl.jovmit.roboapp.RoboTestRunner"

Once we replace, we have to make sure to sync the project so the test runner replacement will get recognized. Once the sync is done, we can go ahead and use custom modules for particular UI tests.

Wrap up

We've seen how we can take advantage of a custom test runner to make our tests more independent, and how we can benefit from it when using 3rd party libraries like Koin. I hope you find this article useful and applicable in your case.

Feel free to follow me on Twitter so you will stay up to date with new content.

Did you find this article valuable?

Support Jov Mit by becoming a sponsor. Any amount is appreciated!