Complete example of testing MVP architecture with Kotlin and RxJava — Part 3
This is the last part of my series about Android testing. If you missed the first 2 parts don’t worry, you can understand this even if you haven’t read them. If you want to check them out anyway, you can find the links below.
In this part you will learn how to create UI tests in Espresso with fake data, how to mock dependencies with Mockito-Kotlin, and how to mock final classes in Android instrumented tests.
Writing Espresso tests with fake data
If we want to write UI tests, which always produce the same results, the most important thing we need to do is to make our tests independent from any data coming from the network, or from our local database.
In other layers we can easily achieve this by mocking the dependencies of the tested classes (as you have seen in the first 2 parts). This is a little bit different in UI tests. In the previous examples our classes got their dependencies in their constructor, so we could easily pass mock objects to the constructor. Android components are instantiated by the system, and they usually get their dependencies through field injection.
There are multiple ways to create UI tests with fake data. First let’s see how can we substitute the UserRepository
with FakeUserRepository
in our tests.
Implementing FakeUserRepository
The FakeUserRepository
is a simple class, what provides us fake data. It implements the UserRepository
interface. This interface is also implemented by the DefaultUserRepository
, what provides us the real data in the app.
I think this code doesn’t need much explanation. We create a Single
that emits a list of fake users. Although it’s worth to mention this part of the code:
val users = (1..10L).map
We can use the map
function to create a list from a range. This can be very useful in cases like this.
Injecting FakeUserRepository into our test
Now we have our fake implementation of the UserRepository
, but how can we use it in our test? With Dagger we usually have an ApplicationComponent
and an ApplicationModule
to provide the app level dependencies. We initialize the component in our custom Application
class.
Now we will create a FakeApplicationModule
and a FakeApplicationComponent
, what will provide us the FakeUserRepository
. In our UI tests we will set the component
field to a FakeApplicationComponent
.
Let’s see this in an example:
The first two snippets were already explained above. The interesting part here is the MainActivityTest
class. Let’s see what happens here.
In the setUp
method we get a reference for the instance of our CustomApplication
class, create our FakeApplicationComponent
and then start the MainActivity
.
It is important to start our Activity after we set the component. This can be achieved by passing an other constructor parameter to the ActivityTestRule
constructor. The third parameter here is a boolean, what determines if the test runner should start the Activity immediately.
An example Espresso test
Now we can start writing some tests. I don’t want to get into the details of how to write test cases in Espresso, there are tons of tutorials out there, but let’s see a simple example.
First we need to add dependencies to our build.gradle
. If we are using RecyclerView
, we also need to add the espresso-contrib
dependency on top of the usual espresso-core
.
And our test looks like this:
What happens here?
First we find our RecyclerView
then perform a click on it’s first (0 index) item with the help of RecyclerViewActions
.
After we make an assertion, that a Snackbar is shown with the text User 1: 100 pts
.
This is a pretty simple test case. You can find more example test cases in the projects Github repository. The code changes of this part can be found in this commit:
https://github.com/kozmi55/Kotlin-MVP-Testing/commit/8152c2065af2e0871ba1175cadecb92b3fa8417f
Mocking the UserRepository in UI tests
What should we do if we want to test the following scenario?
- Load the first page of the data with success
- Load the second page with an error
- Verify if a Toast is shown on the screen, when we try to load the second page
We can’t use our fake implementation here, because it always returns success with a list of users. We can hack the implementation, that for the second page it returns a Single, what emits an error, but this is just not good. If we add another test cases, we need to hack it again and again.
We can mock the behavior of the getUsers
method. For this we need to make slight modifications in the FakeApplicationModule
.
Now we pass the UserRepository
in the constructor, so in our test we can create a mock object, and build our component using that.
This is our modified test class. We are using the mockito-kotlin library, what I have already mentioned in the first part to mock the UserRepository
. We need to add the following dependencies to build.gradle
to use it.
androidTestImplementation "com.nhaarman:mockito-kotlin-kt1.1:1.5.0"
androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:2.2.0'
Now we can modify the behavior of the mock. I created two private utility methods for this, that can be reused in the test cases.
The other change we need to make is to launch the Activity in the test case, after setting up the mock object, instead of doing it in the setUp
method.
With this change our previous test case looks like this:
There are some more test cases in the GitHub repository, including error cases. Changes from this part can be seen in this commit:
https://github.com/kozmi55/Kotlin-MVP-Testing/commit/3889286528ad5a88035358894fda9e0be8c145aa
Bonus: Mocking final classes in instrumented tests
In Kotlin every class is final by default, which makes mocking complicated. In the first part we have seen how can we mock final classes with Mockito.
Unfortunatelly this method doesn’t work in Android instrumented tests. There are a couple of other solutions, what can be used in this case. One of them is the Kotlin all-open plugin.
This is a compiler plugin, that lets us create an annotation, what will make the class open if used on it.
To use it we need to add the following dependency to our top level build.gradle
file:
classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
And add these lines to our app modules build.gradle
file:
apply plugin: 'kotlin-allopen'allOpen {
annotation("com.myapp.OpenClass")
}
Now we only need to create our annotation in the package we specified:
@Target(AnnotationTarget.CLASS)
annotation class OpenClass
Example of the all-open plugin can be found in this commit:
https://github.com/kozmi55/Kotlin-MVP-Testing/commit/8152c2065af2e0871ba1175cadecb92b3fa8417f
We reached the end of our long journey to cover every piece of code in our app with tests. Thanks for reading this far, I hope you found these articles useful.
If you liked it, hit the clap button or share it with fellow Android developers.
If you have any questions or suggestions leave a comment below or you can reach me on Twitter.