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:

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(...).

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:

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(..., ....).

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:

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 **
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:

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:

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:

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):

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

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

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!

Tell us what you think.

Comments are moderated and will only be visible if they add to the discussion in a constructive way. If you disagree with a point, please, be polite.

Subscribe