Setting up Koin for Android UI testing
- 3 minutes read - 580 wordsProblem 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 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.
Follow @jovchem