Android Testing: how to perform unit tests

If you did not read the first part “Android Testing with Kotlin, Mockito and Espresso” I recommend you take a quick look before continuing with this, because I will mention some things that came in it.

In this second instalment, we will see unit tests in greater detail. As already said, developing unit tests and integration tests is very similar and the difference lies in the number of entities, layers and/or scenarios involved in the functionality being tested. To clarify this, we will look at examples of both.

The purpose of this post is to be much less theoretical than the previous one and get our hands dirty, so that we quickly review some good practices when implementing a series of inter-related tests and look at the main functions offered by JUnit and Mockito, at least those that we have found most useful in the teams I have worked in.

Of course, there are many valid ways to face the tests of a given functionality and I certainly do not intend to imply that this is the most correct one, but I think it is a good starting point so that each one of you can later refine your own styles.

As the title of the post says, the idea is to give you a boost to begin with, but it is up to you to research more and get even more out of it.

Building a unit test

We shall start by identifying what we need to test. Following the example of the introduction post, remember that we had among other classes a UserRepository interface and a UserRepositoryImpl implementation.

Interface:

interface UserRepository {
   fun getUser(id: Int): User
   fun updateUser(user: User)
}

Implementation:

class UserRepositoryImpl(private val userDao: UserDao, private val userApi: UserApi): UserRepository {

   override fun getUser(id: Int): User{
       userDao.getUser(id)?.let { user ->
           return user
       } ?: run{
           val user = userApi.getUser(id)
           userDao.storeUser(user)
           return user
       }
   }

   override fun updateUser(user: User){
       userApi.updateUser(user)
       userDao.storeUser(user)
   }

}

We saw that the desired functionality for the getUser(id) function was:

  1. The entity UserDetailPresenter(Presenter) requests the user data through the entity GetUserUseCase(UseCase).
  2. The UseCase, through the UserRepository entity (Repository), obtains the user data  and returns them.
  3. The repository attempts to obtain the data stored locally through the UserDao entity (DAO) and, if not available, gets them through a Web Service using the UserApi entity (API). In the case of the latter, before returning the data, it stores them through DAO to streamline future inquiries.

Although it may seem obvious, let me highlight that the implementations is what is tested, never the interfaces. Therefore, the test subject in this example will be the UserRepositoryImpl class and the objective will be to verify the requirements defined for the UserRepository entity are met, represented by an interface. The examples will focus exclusively on the getUser(id) function.

If you are asking what would happen in the case of abstract classes, the rule remains. An abstract class cannot be instantiated, and therefore cannot be tested on its own.

We must test some of its implementations, although it is true that in this case, part of the logic we are testing is actually implemented in the abstract class. In any case, focus on the idea: we only test implementations.

The most common thing is to create a Test file per class to be tested. This file is just another class that contains the necessary references for the set of tests and series of test functions, each one of them representing a unit test.

In addition, we have a reduced set of functions that are common to all tests, as we will see.

Never test third-party implementations

This needs to be clarified. When working with some of the most common libraries, such as Retrofit, these work by automatically generating an implementation based on an interface that we define in the project, configured using a series of annotations.

Well, we should never test third-party implementations, given that it is something they should have done before publishing the library. As we do not have a proper implementation to test, we save time not having to test the code, with its corresponding time saving.

An example of this would be if we had defined the UserApi interface as follows and had generated the implementation using the Retrofit builder, as usual:

Interface:

interface UserApi {
   @GET("users/{user_id}")
   fun getUser(@Path("user_id") id: String): Call<User>
}

Injection:

Provides
@Singleton
fun provideUserApi(retrofit: Retrofit): UserApi = retrofit.create(UserApi::class.java)

Nomenclature

Android Studio has a wizard to create tests and it suggests the most common nomenclature, which consists in adding the suffix “Test” to the name of the class to be tested. In this case, “UserRepositoryImplTest”.

You will see there are colleagues that simplify the nomenclature to “UserRepositoryTest”, given it is the entity being tested, but this somehow implies there is a single implementation of the interface.

Bearing in mind that what is actually tested is a specific implementation, I personally prefer the first option. Hence, in the event of having more than one implementation, we will not be forced to rename nor does it lead to confusion.

Location

If you use the wizard, you will see that by default the class is added to the same package as the class we are going to test, but with the difference that it hangs from the “test” root directory, at the same level as the “main” directory.

In addition to consistency, having a test class in the same package as the production class allows accessing the “package” visible variables, which is particularly useful for certain test subject variables and modifying or validating them in the test.

However, this kind of visibility has disappeared in Kotlin and being in the same package becomes something more irrelevant, although I believe it continues to be appropriate in order to maintain an organised structure of the test code.

Dependencies

These are the dependencies used for the following examples, added as usual to build.gradle of the app module:

dependencies {
   //...
   testImplementation "junit:junit:4.12"
   testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.0.0"
   //...
}

We use Mockito-Kotlin for convenience, but it is just a library with a series of useful functions that avoid a certain “boilerplate” when it comes to initialising mocks with Mockito and working on them, thanks to the wonders of Kotlin.

It is this library that in turn, through transitive dependence, imports the Mockito library with the most convenient version to operate with each other.

In any case, you can use Mockito directly without Mockito-Kotlin, although allow me to continue using it!

Initialising and ending a test

The test functions are executed in the runner of JUnit as long as the function has the @Test annotation. You do not need to add anything in order for a test class to use the JUnit runner,  given it is the generic runner and any test defined in a class will run on it if not otherwise indicated.

@Test

class SomeClassTest {

   @Test
   fun thisIsATest() {
       assertEquals(4, 2 + 2)
   }

   @Test
   fun thisIsAnotherTest() {
       assertEquals(8, 4 + 4)
   }

}

We can execute the tests individually (by function), by class (executing all tests contained herein) or directly executing all project tests.

@Before

Suppose now that both tests want to verify something that is user related, being a class of this type:

data class User(val id: Int = 0, val name: String? = null)

Going back to the example, we might have something like this:

class SomeClassTest {

   @Test
   fun thisIsATest() {
       val user = User(1234, "name")
       assertEquals(user.id, 1234)
   }

   @Test
   fun thisIsAnotherTest() {
       val user = User(1234, "name")
       assertEquals(user.name, "name")
   }

}

Ignore the nomenclature of these tests; I know I am not following my own recommendations! It is a simple example without a real meaning and hence my licence; I promise to be more coherent with my words in tests we will see later.

The example shows two tests that operate on a similar user, although they are two different instances  created at the start of each test. To avoid this code duplicity, we have a specific function available executed before any test, which we denote with the @Before annotation.

class SomeClassTest {

   lateinit var user: User

   @Before
   fun setUp(){
       user = User(1234, "name")
   }

   @Test
   fun thisIsATest() {
       assertEquals(user.id, 1234)
   }

   @Test
   fun thisIsAnotherTest() {
       assertEquals(user.name, "name")
   }

}

This function is called always before the execution of each test. It is important to pay attention to the nuance “of each test”. I.e. this function is not executed a single time before the tests; instead the @Before function is called each time before executing each @Test  function.

Bearing this in mind, the truth is that each test continues to use a different instance of the User class, but we reuse code and therefore reduce errors and work if a user initialisation varies, given that if we had not made this change, we would not be required to change it in each test function.

Remember that it is important to isolate each test from the others, because contrary to what our arachnid instinct tells us, it is desirable to initialise all the variables involved in the test before execution.

Again, allow me to remind you that this is not production code and we do not pursue high performance tests, or CPU / RAM optimisation; instead we want to execute isolated tests to detect errors as narrowly as possible in an effective manner.

We always have the ability to reuse variables adding some logic to this Test class, as the class is initialised only once.

For example, the are more or less extended patterns to handle flags that indicate if a setUp function has been called and therefore re-initialise certain variables. In practice, this is not necessary, but bear it in mind.

As a recommendation, I suggest you always call this function “setUp()”, given it is a kind of unwritten rule among developers and is commonly used in different documentation. After all, it prepares the environment to execute the following test.

This function is normally where all mocks are initialised and their most generic behaviour is defined, so that it is reused in the greatest number of tests. We can always modify any mock later in the test function itself, which is called just after.

@After

It is just the opposite of @Before. The function marked with this annotation will be executed immediately after each @Test function.

The idea of this function is to release resources or clear certain states, if required in your app after executing a test.

In my experience, the truth that I have rarely needed it, but it has been useful in some very specific cases. This function is normally called “tearDown()” and, again, I recommend you use this name whenever you can, although I have seen a little more imagination when naming this function.

A usage example could be that of a static variable that keeps its value in several tests. Let us say, for economy reasons, that the User class has a static variable (defined in the Kotlin block “companion object”) that indicates if it has been logged or not.

class User(val id: Int = 0, val name: String? = null){
   companion object {
       var isLoggedIn: Boolean = false
   }
}

Assuming that this static variable is modified by the internal execution of a test, this is a good method to reset its value.

We should also have the  option to clear it in the setUp function (@Before) before executing each test, but if we want to keep a more orderly structure we may prefer to group this type of operations in thetearDown() (@After) method.

class SomeClassTest {

   lateinit var user: User

   @Before
   fun setUp(){
       user = User(1234, "name")
//        User.isLoggedIn = false
   }

   @Test
   fun thisIsATest() {
       assertEquals(user.id, 1234)
   }

   @Test
   fun thisIsAnotherTest() {
       assertEquals(user.name, "name")
   }

   @After
   fun tearDown(){
       User.isLoggedIn = false
   }

}

Building the skeleton

In order to maintain a common reference point, we will reuse the example we have mentioned at the start of the post  and which have been using since the introduction post.

We shall now test our UserRepository interface implementation and, as we have seen, this involves creating a test class “UserRepositoryImplTest“.

We already saw in the introduction post that the app architecture was fundamental, and it is now when we benefit the most of it. Let us see what the test skeleton would look like:

class UserRepositoryImplTest {

   //Test subject
   lateinit var userRepository: UserRepository

   //Collaborators
   lateinit var userApi: UserApi
   lateinit var userDao: UserDao

   @Before
   fun setUp(){
       userApi = mock()
       userApi = mock()
       userRepository = UserRepositoryImpl(userDao, userApi)
   }
  
   //... let's do it!

}

In this skeleton, the truth is that the UserApi and UserDao mocks have not been configured to behave in a specific manner, and the consequence of this is that the entire function will return a generic value (0/null) or nothing, in the case of Unit functions (void in Java).

We will also see that the reference to the Repository is kept only to its interface and not the implementation. This is so because, if we have sufficiently decoupled architecture, all interactions to be tested will be through the interface and any other function created in the  implementation that is not part of the interface should be called from one of the functions that are part of it.

Remember that we must create the tests from a perspective of operating with a “black box”. In any case, if you have the need to try another public function not defined in the interface, you can always test the implementation reference.

Going back to the example, the behaviour we will give the series of mocks will vary depending on the test, but I personally think that it is good practice to apply the most common configuration in setUp(), lightening some test functions that will use this configuration.

In this case, imagine we want to start testing how the function behaves when both collaborators return a User on request, without errors.

As we already saw, there are several tests to be carried out only under this scenario; hence we can apply this generic configuration knowing it will be reusable in a considerable number of tests.

Let us have a look:

class UserRepositoryImplTest {

   //Test subject
   lateinit var userRepository: UserRepository

   //Collaborators
   lateinit var userApi: UserApi
   lateinit var userDao: UserDao

   //Utilities
   lateinit var userFromApi: User
   lateinit var userFromDao: User

   @Before
   fun setUp(){
       //Mocking UserApi
       userApi = mock()
       userFromApi = User(0, "fromApi")
       whenever(userApi.getUser(any())).thenReturn(userFromApi)

       //Mocking UserDao
       userDao = mock()
       userFromDao = User(1, "fromDao")
       whenever(userDao.getUser(any())).thenReturn(userFromDao)

       userRepository = UserRepositoryImpl(userDao, userApi)
   }

   //... let's do it!

}

This is what we did:

  • We have added two fields that keep the reference to User objects that we want to represent users returned by the API and DAO in these tests, whenever requested.
    • We could have reused a single object for both, but it would be more useful for the tests to have two separate references in order to discern if the user returned by the Repository is actually the user returned by one of these collaborator classes or if it was recreated or adulterated along the way.
    • Actually, we could even have mocked these users, assuming that the User class is open (in Kotlin) and not final (in Java), but we would lose the advantage  of knowing what is the exact content of these objects (ID and name), which could be useful in some tests.
  • We have initialised the mocks for the API and DAO. Mockito internally generates an implementation of the interfaces that represent the variables, which is achieved  with Kotlin in such a simple way thanks to the type inference.
  • We have instantiated two users, one per collaborator class, giving them different values.
  • We configured the mocks so that, each time the getUser(id) function is called, it returns the referenced User object.
    • Specifically, this configuration indicates that the referenced User objects to be returned regardless of user ID passed by parameter.

As we move forward we will go some way to discuss in more detail how the blocks we use work.

whenever(…).thenReturn(…)

We reached the first and possibly most used block in Mockito; in this case slight adapted by Mockito-Kotlin.

The methods nomenclature is quite good and I am sure you can guess how it works; we will go into this next.

whenever(…)

The first parameter for the “whenever” function we must reference a specific function from a Mockito mock.

If for example the userApi variable had been initialised in a normal way (instead of with the “mock()” function), it throws an exception when executing the test, because the ordinary implementation does not have the necessary capabilities to work with Mockito.

This first block indicates that something must be done to call this function, which we will indicate in the second block.

Should the function to be mocked has a parameter, it is also important to indicate what filter must be fulfilled. I.e., if instead of any(), which denotes any value, we had indicated “2”, the mock would only return this user when requesting the user with ID value of 2.

We have several predefined “any()” functions, such as anyString() or anyInt(), when we want to delimit the type, but we can use the any() for more complex or custom objects as we are doing, which will infer the correct type for the specific input parameter of the function being mocked.

whenever(userApi.getUser(any())).thenReturn(userFromApi)

I recommend using the any() function whenever the type is not relevant for the test. I.e. in this case we are perfectly aware the ID is type Int, and therefore the anyInt() function could be used perfectly and even define the parameter type we want to consider in our mock.

However, in the event that the ID changed to a type String due to a change in requirements. Beyond the problems of refactoring the production code, our tests would also fail during compile time.

This is because anyInt() returns an integer (even if it is a matcher) and it would no longer be valid as a parameter type of the function, which is now a String. Instead, using any() and through type inference, the mock would continue to compile and simply the new implementation returned by this function would be type String.

thenReturn(…)

This function defines the object to be returned for the condition set in the “whenever(…)” block.

The value returned shall coincide with the type returned by the mocked function; otherwise the compiler will return an error.

As already discussed (although it may not be appropriate for these specific tests), we could return another mock and simplify the code, so bear this in mind because in some cases you will not want to work with the returned object and it will be very helpful:

whenever(userDao.getUser(any())).thenReturn(mock())

thenAnswer{…}

This function is an alternative to the previous function and allows us to work with a more elaborate response, defining a code block which ultimately must contain the object to be returned. For example:

whenever(userDao.getUser(any())).thenAnswer {
   val user = User()
   //do something...
   user
}

We can throw exceptions in this block, for example, and it is quite useful when we want to condition the response to a series of more dynamic factors.

doAnswer{…}.`when`(…).<fun>

This block is alternative to the “whenever(…).thenAnswer(…)” block seen above, with a similar functionality.

The fact of mentioning in this post is to share a small trick. In our last project, we detected in my work team that when throwing exceptions, the block normally used for it did not work correctly with Kotlin:

whenever(userDao.getUser(any())).thenThrow(Exception())

However, this alternative block did behave as we expected:

doAnswer { throw Exception() }.`when`(userDao).getUser(any())

It is possible this behaviour no longer repeats itself by the time you read this, but if you find this problem here is a solution that I hope will save you a few hours “scratching your head”.

This is another example that there are alternative structures to generate the same configuration in the mock, and probably none are better than others so you can choose whichever is most comfortable for you.

If you are wondering the reason behind the quotes surrounding the “when”, the answer is that it is a reserved word for a type of block and this is how we tell the compiler that we are referring to the name of a method and not said block.

Our first tests

We are finally in conditions to implement our first unit test!

We shall start by checking a pair of requirements:

  • Whenever you request a user from the Repository, it is requested from DAO with the data provided (the ID in this case).
  • Whenever the DAO returns a user, the Repository will return the same user, unadulterated.
  • Whenever the DAO returns a user, it is not requested from the API.

For these we will use a scenario in which the DAO effectively returns a User. This scenario is contemplated in the mock configuration defined in the setUp; hence it is not possible to limit the test functions to applying verifications.

class UserRepositoryImplTest {

   //Test subject
   lateinit var userRepository: UserRepository

   //Collaborators
   lateinit var userApi: UserApi
   lateinit var userDao: UserDao

   //Utilities
   lateinit var userFromApi: User
   lateinit var userFromDao: User

   @Before
   fun setUp(){
       //Mocking UserApi
       userApi = mock()
       userFromApi = User(0, "fromApi")
       whenever(userApi.getUser(any())).thenReturn(userFromApi)

       //Mocking UserDao
       userDao = mock()
       userFromDao = User(1, "fromDao")
       whenever(userDao.getUser(any())).thenReturn(userFromDao)

       //Test subject initialization
       userRepository = UserRepositoryImpl(userDao, userApi)
   }

   @Test
   fun repositoryAsksForUserToDaoWithProperUserId(){
       val userId = (0..10).random()
       userRepository.getUser(userId)
       verify(userDao, times(1)).getUser(userId)
   }

   @Test
   fun ifDaoReturnsUserThenApiIsNotCalled(){
       userRepository.getUser(0)
       verify(userApi, never()).getUser(any())
   }

   @Test
   fun ifDaoReturnsUserThenRepositoryReturnsSameUser(){
       val user = userRepository.getUser(0)
       assertEquals(user, userFromDao)
       assertEquals(user.id, 1)
       assertEquals(user.name, "fromDao")
   }

}

Tests pass correctly! We shall now review what we have done:

verify(…, ….).<fun>

The “verify” function allows us to check the number of calls to a given function. Again, the first parameter must respond to a mock initialised with Mockito or it will throw an exception.

The second parameter indicates the number of times the call should have been made. The times(…) function is the simplest way to define this number, but there are other predefined functions that can be useful, especially if the expected number can vary:

  • never(): never called.
  • AtLeast(numOfTimes): it must be called at least the specified number of times.
    • AtLeastOnce: at least once.
  • AtMost(numOfTimes): not more than the specified number of times,
  • only(): it was called and was also the only function called.

In order to verify it has been called only once, we must not pass this second parameter and initialise it internally with the value“times(1)”.

The word <fun> references the name of the function, which must be one of the functions that can be called in the mock previously passed with a parameter. If this function has any parameters, as in this case, it must match the parameter we expect or be some kind of generic matcher, such as any().

In this case, we used a random to verify the ID is correct and that, furthermore, it was not just “luck” that we tested an ID that is hard coded internally, because in this case it would throw an error in one of the executions.

The reason why we stopped using random in the following tests and accept any ID (using the any() function) is that we want to avoid masking.

If we always test for the correct ID requirement, in the hypothetical case that the implementation of the Repository should change passing a value equal to ID+1 to the DAO/API, all tests would fail despite not being the behaviour the vast majority of them was verifying.

However, as we shall see a little further on, this isolation must be rationalised.

On the other hand, when we work with these Mockito mocks, all interactions on them are registered for subsequent testing. That is why the“verify” function is executed after calling the function we want to validate.

With this in mind, it now becomes more apparent that the mocks need to be reinitialised before executing each test (in the setUp for example), so that these interaction counts are restored for the next test.

assertEquals(…, …)

The “assert” functions are some of the main tools when it comes to verifying a condition we believe must be met for a test to pass.

There is a large number of them, although probably the most used is “assertEquals”, which verifies that two parameters are equal, allowing for different types of parameters, as we shall see in the example (objects, String, Int, etc.).

I encourage you to research a little more on the various types of assertion that exist; these are some of the most common, which I believe do not need explanation:

  • assertTrue
  • assertFalse
  • assertNull
  • assertNotNull
  • assertArrayEquals

In the case of more complex conditions, we have an “assertThat” function that allows us to define a matcher for the condition, which is a somewhat more elaborate comparison function.

If you are wondering why we called the getUser function with an ID value of 0 in the second function, and however the assertion checked is that the ID is 1 is correct; let me remind you that when we “mocked” the function, we did so that regardless of the ID passed, it would return a User with the ID value of 1.

I guess this could have led you to confusion, but I thought it was a good example to practice with these incoherences that, in reality, are very coherent.

fail()

It is interesting to know thefail() function, which basically causes an error in the test equivalent to an “assert” not  being fulfilled.

The truth is that it is very useful, given that sometimes the comparison we need to make is so complex that most practical result is to do it “by hand” than using these “assert” functions, hence if not fulfilled we can get the same result by calling this“fail()” function.

But, are you not testing several things at the same time?

Before continuing, I would like to mention there are those who believe that a test should have only one verification (a single assert or verify, for example) to be truly unitary; therefore the most correct thing would be to repeat the scenario as many times as necessary and identify what we are going to prove in each test.

Therefore, the example of the third function should be divided into three and differentiate between the test that asserts the object is the same, the one that verifies the ID has not been modified and the one that verifies that the Name has not changed.

Note: in Kotlin, if we define User as a data class, the first check implies the following two, given that the generated hashCode is based on the values of its properties.

If we are demanding, even in the first example we are not only verifying that the DAO is called with the correct ID, but we also check it is called only once.

It is possible that this rule is correct from an orthodox point of view, but from a practical point of view we have always agreed in the teams I have worked in that it is more convenient to group validations as long as it is a well-defined test, although as usual, this is open to opinions and interpretations.

As mentioned in the introduction, we must know how to measure and find the balance so that we do not spend too much time developing tests, but without giving up a reasonable level of detail.

If you accept some advice, I recommend you start performing checks as a whole (careful, as long as it is a clearly defined test) and, if you have time, segment it later  into more isolated checks, so that you have sufficiently descriptive tests from the start and, gradually, you increase their level of detail.

Again, everything will depend on the project context and the times you manage.

We continue testing!

We shall now test another series of requirements:

  • If the DAO does not return the User, it is requested from the API.
  • It is always requested from DAO before requesting it from the API.
  • Whenever you get a user from the API, it is also stored through DAO, unadulterated.

For simplicity, we will reduce the examples to the test functions. As long as I do not tell you otherwise, the setUp() function, as well as the fields defined at class level will remain unchanged from the initial example.

@Test
fun ifDaoDoesNotReturnUserThenRepositoryAsksToApi(){
   whenever(userDao.getUser(any())).thenReturn(null)
   userRepository.getUser(0)

   verify(userDao, times(1)).getUser(any())
   verify(userApi, times(1)).getUser(any())
}

@Test
fun UserIsAskedToDaoBeforeAskingToApi(){
   whenever(userDao.getUser(any())).thenReturn(null)
   userRepository.getUser(0)

   val orderVerifier: InOrder = inOrder(userDao, userApi)
   orderVerifier.verify(userDao).getUser(any())
   orderVerifier.verify(userApi).getUser(any())
}

@Test
fun 

In the first function, we are not doing anything new. However, it is worth noting that the test scenario should change for this test compared to the default defined in the setUp().

Specifically, in this case, we must return null in DAO to verify that the API responds as defined in the requirements.

This merely requires replacing the behaviour of the mock configuring it again, given that this test function is called just after the setUp function, as already seen.

Later, we simply confirm that the getUser() method has been called in both entities.

InOrder

The second function brings novelties. Now we want to verify not only that certain functions have been called, but that the order is correct.

This only requires initialising the InOrder object passing it the constructor of the mocks involved in this verification. Later the verification is similar to the one we have already seen, only that we must call it through the “verify” function of this InOrder object.

If you find it useful, this “verify” function also supports a VerificationMode; i.e. as we have been doing, we can delimit the number of interactions  with the same set of times(), never(), atLeast(), etc. functions.

ArgumentCaptor

TheArgumentCaptor(KArgumentCaptor with Mockito-Kotlin) is a truly useful class for checking the input parameters of a function.

In the example of this third test function we check that the returned user from the API is stored through DAO.

By configuring the API mock we defined which specific User object it should return each time the getUser function was called, as is the case with this test.

However, as we must reconsider the test as if the Repository were a black box, we have no certainty that the user stored is actually returned by the API. In the worst-case scenario, the Repository could have created a new user and filled in its details with hard coded values.

Well, this is what we must test with the ArgumentCaptor test. It allows us to capture the arguments exchanged between objects. In order to use it, simply initialise it by defining the type of argument we are going to capture and, later, capture it with a verify block.

Then we will have the value available as shown in the example. A function can have a vararg type input parameter (indefinite number of elements); this Mockito-Kotlin implementation handles in a declarative manner the first three values (firstValue, secondValue, thirdValue), although we can get a complete list of arguments handled internally by Mockito from the “allValues” variable, which is a list of elements.

In practice, we will almost always use the “firstValue” or simply “value”, if you work directly with Mockito.

Going back to the example, and knowing that we have forced the API to return the user “userFromApi“, what we do is to ensure that this same instance is stored through DAO using its storeUser function.

To be more comprehensive, we could verify as done previously that the ID and Name have not been modified, but I leave that to you!

Error testing

Proving that the behaviour is as expected when everything goes well is only a part of the job. The truth is that sometimes it is even more important to prove that the test entity behaves as expected when something fails.

How to do these tests. First, we will slightly modify the implementation of the getUser function in our UserRepositoryImpl class in order to exemplify these tests, given we have not treated errors.

override fun getUser(id: Int): User{
   userDao.getUser(id)?.let { user ->
       return user
   } ?: run{
       val user = userApi.getUser(id)
       try {
           userDao.storeUser(user)
       }catch (e: Exception){
           throw IllegalArgumentException("Storing failed!", e)
       }
       return user
   }
}

The new requirements are, even if devoid of sense:

  • Any exception that occurs when a User is recovered, regardless of its origin (API or DAO) must be propagated without any modification.
  • Any exceptions in the API  or DAO are represented by an IllegalStateException.
  • Any exception thrown when storing a user through the DAO is wrapped in an IllegalArgumentException whose message must be “Storing failed!”.

The tests!

@Test(expected = IllegalStateException::class)
fun whenDaoFailsRecoveringUserAnIllegalStateExceptionIsThrown(){
   doAnswer { throw IllegalStateException() }.`when`(userDao).getUser(any())
   userRepository.getUser(0)
}

@Test
fun whenDaoFailsRecoveringUserTheExceptionIsPropagatedAsIs(){
   val exception = IllegalStateException()
   doAnswer { throw exception }.`when`(userDao).getUser(any())

   try {
       userRepository.getUser(0)
       fail()
   }catch(e: Exception){
       assertEquals(e, exception)
   }
}

@Test(expected = IllegalStateException::class)
fun whenApiFailsRecoveringUserAnIllegalStateExceptionIsThrown(){
   whenever(userDao.getUser(anyInt())).thenReturn(null)
   doAnswer { throw IllegalStateException() }.`when`(userApi).getUser(any())
   userRepository.getUser(0)
}

@Test
fun whenApiFailsRecoveringUserTheExceptionIsPropagatedAsIs(){
   whenever(userDao.getUser(anyInt())).thenReturn(null)
   val exception = IllegalStateException()
   doAnswer { throw exception }.`when`(userApi).getUser(any())

   try {
       userRepository.getUser(0)
       fail()
   }catch(e: Exception){
       assertEquals(e, exception)
   }
}

@Test
fun whenDaoFailsStoringUserTheExceptionIsWrappedProperly(){
   whenever(userDao.getUser(anyInt())).thenReturn(null)
   val exception = IllegalStateException()
   doAnswer { throw exception }.`when`(userDao).storeUser(any())

   try {
       userRepository.getUser(0)
       fail()
   }catch(e: Exception){
       assert(e is IllegalArgumentException)
       assertEquals(e.cause, exception)
       assertEquals(e.message, "Storing failed!")
   }
}

In the first function we add an additional behaviour to the mock. As described in the new requirements, the DAO on error will throw an IllegalStateException, and this is how we configure it for this test.

The objective is exclusively to verify that the error type is as expected. To do this, the @Test annotation has the capacity to define the type of Throwable that can be thrown as a result of the test run, making it very easy to validate.

In the second function, what we want to validate is that the exception propagates without modification, as thrown from the DAO (the same instance of the exception).

To do this, the block that we discussed earlier in the post and that is necessary due to a Kotlin incompatibility as at the date of writing this post to throw exceptions in a simple manner (with the doThrow function).

As we expect an exception to be thrown, the fail() function also comes in very handy, allowing us to force the test error if we reach this line (i.e. no exception was thrown).

The third and fourth functions are equivalent, but for the API. In this case, as the API is only called if the DAO does not return any data, we must also add this configuration to the mock.

Finally, in the last function, we perform some additional checks, although the block is similar to the others.

Specifically, we confirm that the exception returned is not of the original type, but an IllegalArgumentException, as defined in the requirements. In addition, we check that this exception actually wraps the original (cause) and contains the correct message.

No more excuses

At this point, the truth is that you have acquired enough knowledge to test, probably, 90% of the business logic in your app through unit tests, from the presentation layer (without including views) to data access.

There will be simpler and more complex tests, but if the architecture focuses on the testability of the app, as we saw in the introduction post, I assure you will be able to face the vast majority of tests with this limited set of functions.

Naturally, a time will come when you will find some limitations or complications that will require some additional functions or a more complex mock configuration, but there is considerable documentation on it in the network; so do not be afraid.

Integration Tests

Promises are made to be kept, so before I finish this post, we shall see that defining an integration test is very simple. You will see that there are no differences from Mockito’s perspective and its functions. The only difference is established through initialisations.

Suppose we want to test an integration test that checks that a stream is correct from the moment the Presenter receives the click event (which represents the  trigger to get a user), until the DAO is called, and back, until the view receives the element to display. In this case, we want to carry out a series of tests based on the different data types that the DAO or API may return, as we have been doing at a Repository level.

Well, in this case, we will involve the following entities (real, no mocks):

  • UserDetailPresenter
  • GetUserUseCase
  • UserRepository

In addition, in order to control the recovered User data, we will re-inject the mocks of the following entities:

  • UserDao
  • UserApi

Furthermore, as a novelty, we inject the mock of the view interface to the Presenter, as we are working in an MVP architecture.

  • UserDetailView

This is the class structure we have:

View (interface)

interface UserDetailView {
   fun doSomethingWithUser(user: User)
}

Note: the implementation of this view could be, for example, a Fragment, but it is not important in this example.

Presenter (interface):

interface UserDetailPresenter {
   fun onClick()
}

Presenter (implementation):

class UserDetailPresenterImpl(private val userDetailView: UserDetailView, private val getUserUseCase: GetUserUseCase) : UserDetailPresenter {
   override fun onClick() {
       val user = getUserUseCase.getUser(0)
       userDetailView.doSomethingWithUser(user)
   }
}

UseCase (interface)

interface GetUserUseCase {
   fun getUser(id: Int): User
}

UseCase (implementation)

class GetUserUseCaseImpl(private val repository: UserRepository) : GetUserUseCase {
   override fun getUser(id: Int): User = repository.getUser(id)
}

The Repository, DAO and API you already know well.

Nomenclature

When it comes to naming the Test class, it is now much more open, as it will depend on what we are going to test. As a recommendation, I suggest you keep at least one common suffix for this type of “IntegrationTest“. In the example, we will name it “RecoveringUserIntegrationTest“.

class RecoveringUserIntegrationTest {

   //Test subject
   lateinit var userDetailPresenter: UserDetailPresenter

   //Collaborators (no mocks)
   lateinit var getUserUseCase: GetUserUseCase
   lateinit var userRepository: UserRepository

   //Collaborators (mocks)
   lateinit var userDetailView: UserDetailView
   lateinit var userApi: UserApi
   lateinit var userDao: UserDao

   @Before
   fun setUp() {
       //Init Mocks
       userDetailView = mock()
       userApi = mock()
       userDao = mock()

       //Collaborators initialization (no mocks)
       userRepository = UserRepositoryImpl(userDao, userApi)
       getUserUseCase = GetUserUseCaseImpl(userRepository)

       //Test subject initialization
       userDetailPresenter = UserDetailPresenterImpl(userDetailView, getUserUseCase)
   }

   @Test
   fun whenDaoReturnsUserItIsPropagatedToTheViewAsIs(){
       val testUser = User(2, "integrationTest")
       whenever(userDao.getUser(any())).thenReturn(testUser)

       userDetailPresenter.onClick()

       val captor : KArgumentCaptor<User> = argumentCaptor()
       verify(userDetailView, times(1)).doSomethingWithUser(captor.capture())
       assertEquals(captor.firstValue, testUser)
       //Validate ID/Name if wanted
   }

}

As I am sure that you understand perfectly, what we have done is set up the DAO mock to return a specific user and check that the mock of the view receives that same user.

Remember we must maintain a black box approach, where we can only manipulate the ends, which in this case are on one hand the View and the DAO and API on the other. This is why these are the only classes that have been mocked.

On the contrary, all other classes involved use the same implementation to be used in the app in a real-world scenario. Although we referenced the Presenter as the subject of our tests (which we interact through), as we have seen, all classes involved are really test subjects of this test.

We will know which specific flow failed if the test fails (the User propagation from DAO to the View), but not the exact point within this flow.

Interface (UI) test

We could say that you are no longer a novice on unit tests and their cousins: the integration tests.

For these UI tests, however, the paradigm changes a little. Everything we have learned until now continues to be very useful, but we must use other functions from a different testing framework: Espresso.

We will see it in detail in the next post (with a bonus related to Kotlin and its infix functions). See you there!

Foto de jgironda

I'm an Android Developer by profession for about 5 years, although I was already messing with the development of Android applications on my own since its first versions, when I was still in college. I'm passionate about everything to do with technology and particularly robotics. As hobbies, I don't miss any Real Madrid game and I love the series.

See all Jorge Gironda activity

Escribe un comentario