Android view unit testing recipe
Developing the android-base project, I’ve learnt how to cook delicious unit tests, specially designed for the Android view layer. Today I’m going to explain how I prepare them.
With this recipe, you’ll be able to test fragments and activities behavior, making them play like puppets. It can seem complicated to cook and the tests will continue being Android dependent, but the results are very grateful… So I strongly recommend you to try it at home!
Basic Ingredients
Method
1. Make an app that’s easy to test
The basic ingredient of this recipe is an app to be tested, of course. But a simple app doesn’t serve, it must be an app made to be tested. It needs to be implementated following the MVP pattern mixed with the dependency inversion principle (I don’t mean it’s the only way to make an app easy to test, I just list the patterns that we’ll need here).
The views must be passive and free of any logic, moved like puppets by the corresponding presenter. This one is in charge of receiving the view events and send orders to it. The dependency injection with Dagger makes the component replacement easier. Thus, on testing we will be able to mock the presenter and inject it to the view, send our own orders to it and check the callbacks.
We will then be able to test a more concrete chain of events. Furthermore, we isolate the rest of the app, focusing just on possible view related errors.
Note: Actually, ‘views are completely fool’ is a little white lie. Due to Android devices fragmentation, the views are structured in fragments, whom are included in activities depending on the size of the screen. These activities change, for example, if the app is for mobile or tablet, and its navigation too. For this reason, navigation must be placed inside the activities (but just navigation, the rest of the logic must be outside the views).
2. Gradle the ingredients together
Add the android-apt library,
necessary to compile Dagger files, to the project root build.gradle
:
Then add all the necessary libraries to your project specific build.gradle
and define the testInstrumentationRunner
:
Remember to apply the android-apt
plugin and add the dagger-compiler
with androidTestApt
instead of androidTestCompile
.
Libraries summary:
- JUnit is the framework to structure the tests.
- Mockito will let us mock objects to define their behavior and know when they’re called.
- DexMaker is needed to run Mockito on Instrumentation tests.
- AndroidSupportAnnotations is defined here just to avoid a version dependency problem (because it’s included in other libraries).
- Espresso is the framework to write Android UI tests (espresso-intents are added to test navigation that, as I’ve said, is responsability of the activities).
You need to understand the role of each library to follow this tutorial.
Note: Mockito cannot be updated to version 2
because of some dexmaker
issues.
3. Prepare your first test (not working yet)
You can do it to your taste. I usually launch the activity and get the target test fragment at the setup. Then, I get its presenter and replace it by a mocked one. Here is a test example for a two text field model edition view:
This approach seems to work (and it actually does it most of the times) but it has 2 critical and unacceptable errors:
- You cannot know what has happened with the presenter before the mock replacement.
- Real presenter is initialized and, consequently, all the other components (data repositories, interactors…) when this screen is opened… and that’s not unit testing anymore.
So don’t taste it yet, let’s keep cooking!
4. Make the DaggerTestComponent
We need to take action before the presenter injection is done.
That’s the reason why we have to replace the Dagger Component
and, to do it,
first we must create a test mocker Component
.
The plan is to send that test Component
to inject mocked presenters
to the fragments without the knowledge of them.
Create an interface and move there
all the methods from the old dagger Component
.
Then, inherit the new interface from the old real dagger component:
And from the new mocker component too:
Finally, implement the module for the test component to return a mocked presenter when required.
Now, we must use the FragmentInjector
type in the fragments, being it
an ActivityComponent
or a TestMockerComponent
depending on the case
(make the necessary code adjustments to achieve that).
5. Add a test Application
To deal the mocker injector component to the fragment
(who executes the self-injection)
we must move the responsability of the component creation
to the Application
class and replace it by another one
that will return the mocker component.
In my project I have a Dagger Component for the activity scope, and I used
to generate it in the Activity
(asking the Application
for the ApplicationComponent
).
With this structure we must move the creation of the ActivityComponent
to
the Application
class too, but we can keep the scope by saving the instance on
the activity as a singleton
(the Application
just create a new one on each call).
Note: The ActivityComponent
is initialized before onCreate
call
to avoid fragment’s onCreate
being called first
(
like in this case
), that would cause an attempt to fragment injection with a null injector.
Once that is done, we must structure the Application
class in such way that
we’ll be able to override just one method to replace the mocker component.
BaseApplication
class:
TestMockerApplication
class:
Note: In this step we can appreciate the scope of each Dagger component,
more specifically ApplicationComponent
and ActivityComponent
.
You must try to adapt it to your own project needs, always keeping
the instances saved in their corresponding place to avoid modifying their scope.
6. Insert the secret ingredient
To replace the BaseApplication
class for the test one we need to create
a test runner, where we’ll be able to select the Application
class to be used.
Inherit from AndroidJUnitRunner
, override the method newApplication
and place there the name of your TestMockerApplication
.
Change the instrumentation runner in your project build.gradle
,
replacing the package name:
At last, the mocked presenter is injected automatically to the fragment, so you can remove its replacement and verify their initialization calls. You can now fix the previous test example:
Also notice that now the test class is marked as @SmallTest
.
That’s because the rest of the app classes
(that includes database, network, threating… classes)
won’t be used by the mocked presenter
(info about
test sizes)
because it works as a firewall.
And that’s it! Enjoy your meal!
Conclusions
With these method implemented, view testing becomes easy peasy. Be creative with the possibilities of this kind of testing, and enjoy it!
Also, you can modify a little bit this recipe and create integration Instrumented tests with a fake api (using MockWebServer or creating a custom one) or other kind of custom tests…
I hope this can be of any help. Don’t hesitate to ask or make suggestions!
Used resources: