Complete example of testing MVP architecture with Kotlin and RxJava — Part 1
--
Recently I created a playground project to learn more about Kotlin and RxJava. It’s a pretty simple project, but there was one part, where I had some struggle: testing.
Testing in Kotlin can have some pitfalls, and since it’s quite new, there are not so much examples out there. I thought it would be a good idea to share my experience to help you avoid my mistakes.
About the architecture
The app follows a basic MVP architecture with the repository pattern. It uses Dagger2 for dependency injection, and RxJava2 for data flow.
The repository provides the data from network or local storage, depending on different conditions. We use Retrofit for network calls, and Room for local database.
I won’t get into details about the architecture and these tools. I think most of you are already familiar with them. You can check out the starter project in this commit:
https://github.com/kozmi55/Kotlin-MVP-Testing/commit/ca29cad1973cd434ffb0b0d23c4465fc54e05c0b
We will start with testing the database and then go upwards in the layers.
Testing the database
For the database we are using Room Persistence Library from the Android Architecture Components. It is an abstraction layer over SQLite, that reduces boilerplate code.
This is the easiest part. We don’t need to do anything specific regarding Kotlin or RxJava. Let’s look at the code of the UserDao
interface first, to decide what should we test.
The getUsers
function requests the next 30 users from the database depending on the page.
The insertAll
inserts all the users in the list.
We can see a couple of things here, what needs to be tested:
- Check if the inserted users are the same as the retrieved users.
- Check if the retrieved users are ordered correctly.
- Check if we insert a user with the same id, it will replace the old record.
- Check if we query a page, it will have a maximum of 30 users.
- Check if we query the second page, we will get the correct number of elements.
The below code snippet shows the implementation of the 5 cases.
In the setup
method we need to configure our database. We use Room’s in memory database to create a clear database before each test.
The tests are pretty simple here and don’t need further explanation. The basic pattern we follow in each test looks like this:
- Insert data to the database
- Query data from the database
- Make an assertion about the retrieved data
We can use the functions from the Kotlin Collections API to simplify the creation of test data, like this part of the code:
val users = (1..40L).map { User(it, "Name $it", it *100, "url") }
We create a range, then map it to a list of users. There are multiple Kotlin concepts used here: ranges, higher order functions, string templates.
Commit: https://github.com/kozmi55/Kotlin-MVP-Testing/commit/8cebc897b642cc843920a107f5f0be15d13a925c
Testing the UserRepository
For the repository and interactor we are going to use the same tools.
- Mockito for mocking the dependencies of the classes.
- TestObserver for testing Observables (Singles in our case)
But first we need to enable the option to mock final classes. In Kotlin every class is final by default. Fortunately Mockito 2 already supports mocking final classes, but we need to enable this.
We need to create a text file in the following location: test/resources/mockito-extensions/
with the name org.mockito.plugins.MockMaker
and with the following text: mock-maker-inline
Now we can start writing our tests using Mockito. First we add the dependency to the latest version of Mockito and JUnit.
testImplementation 'org.mockito:mockito-core:2.8.47'
testImplementation 'junit:junit:4.12'
The code of the UserRepository
is shown in the following snippet:
In the getUsers
method we create a Single
, what will emit the users or an error, if occurs. Depending on different conditions the shouldUpdate
method decides, whether the users should be loaded from network or from local database.
One more thing to note is the CalendarWrapper
field. It’s a simple wrapper with one method, what returns the current time. With the help of this we can mock the time in our tests.
So what should we test here? The most important thing to test here is the logic behind the shouldUpdate
method. Let’s write some tests for it.
The way to test this is to call the getUsers
method, and on the returned Single
call the test
method. The test
method creates a TestObserver
and subscribes it to the Single
.
The TestObserver is a special type of Observer, that records events and allows making assertions about them.
We also have to mock the dependencies of the UserRepository
, and stub some of their methods to return the data, what we specify. We can do this with Mockito the same way as in Java, or with the Mockito-Kotlin library by Niek Haarman. We will use Mockito in this example, but you can check the Github repository if you are curious.
If we want to use the when
method from Mockito, we need to put it between backticks, because it is a reserved word in Kotlin. To make this look better, we can import the when
method with a different name using the as
keyword.
import org.mockito.Mockito.`when` as whenever
Now we can use the whenever
method for stubbing.
Above we can see the code of UserRepositoryTest
. We are using Mockito annotations in this example to initialize the mocks, but it can be done in different ways. Every test consists of 3 steps:
- Specify what values should the stubbed methods return. We use the
setUpStubbing
private method to avoid boilerplate code in our tests. We can call this method in every test case with different parameters, depending on what state is under test. Kotlin’s default arguments can be really helpful here, because sometimes we don’t have to specify every parameter. - Call the
getUsers
method, and acquire aTestObserver
by calling thetest
method on the returningSingle
. - Make some assertions on the
TestObserver
or on the mock objects to verify the expected behavior. In this example we use theassertNoErrors
method, what verifies that theSingle
doesn’t emit an error. The other method we use is theassertValue
. With the help of this we can make assertions about the value emitted by theSingle
. The way to do this is to pass a lambda to theassertValue
method, which returns a boolean value. If it returns true, the assertion will pass. In this case we verify, that the emitted list contains 1 element. There are plenty of other methods to make assertions on theTestObserver
, these can be found in the documentation of BaseTestConsumer, what is the superclass of TestObserver.
Changes can be found in this commit:
https://github.com/kozmi55/Kotlin-MVP-Testing/commit/17fc4645bb446879a0e44560c19d6c2c36810a89
Testing the GetUsers interactor
The method of testing the GetUsers
interactor is similar to the one that we used to test the UserRepository
.
GetUsers
is a really simple class, it’s purpose is to transform the data coming from the data layer to data, what represents the users in the presentation layer.
We use some transformations here from RxJava and also from Kotlin Collection API to achieve the desired results.
Let’s see how our tests look like.
The only difference here is that we are creating a fake Single object, what will be returned from the repository’s getUsers
method. We are passing the UserListModel
what should be emitted by the Single to the setUpStubbing
method, where we create our fake Single, and set it as the return value of the repository’s getUsers
method.
The rest of the code uses the same concepts, that we have seen in the UserRepositoryTest
.
Commit after writing GetUsersTest
:
https://github.com/kozmi55/Kotlin-MVP-Testing/commit/49652a53813f004b2c11f962d8ba5666575365fc
That’s all for the first part. We learned how to tackle some common problems while testing in Kotlin and using RxJava, how to utilize some Kotlin features to write simpler tests, and also took a look at testing the Room database.
In the second part I will show you how to test the Presenter with the help of TestScheduler, and how to make UI tests with fake data using Espresso. Stay tuned.
Thanks for reading my article.
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.