In 2020 I had an opportunity to present my thoughts about crucial element of unit testing - test doubles. I discussed various examples of test doubles, ways to implement them and how they fit into specific test cases. Links to the conference talks you can find at the bottom of the article.

Every talk had time for Q&A, and now I answer some of the questions that were asked.

How to approach testing private methods?

tl;dr: you don't test it directly, but within context

Let's take a look at example:

class ShouldITestPrivateMethods {
    fun processSomething(input: Int): String {
        return if (input >= 420) {
            "Number is greater or equal 420"
        } else {
            "You need to add ${420 - input} to get 420"
        }
    }
}

This class is pretty simple - returns some text based on input number. There is no private methods here. We write parameterized test for some cases and we compare result to expected values.

But during refactor we could've change it to something like this:

class ShouldITestPrivateMethods {
    fun processSomething(input: Int): String {
        return if (input >= CONSTANT_NUMBER) {
            "Number is greater or equal $CONSTANT_NUMBER"
        } else {
            "You need to add ${calculateDifference(input)} to get $CONSTANT_NUMBER"
        }
    }

    private fun calculateDifference(input: Int) = CONSTANT_NUMBER - input
    
    companion object{
        private const val CONSTANT_NUMBER = 420
    }
}

Now we have private method here. Do we need to test that private method? If we provided enough reasonable test cases for method processSomething() then we should be secure about that.

On the other hand, if for some reason that calculation become more complex, we could think about extracting this private method into separate class, that would be tested alone in isolation.

class DifferenceCalculator {
    fun differenceBetween(first: Int, second: Int): Int {
        return first - second
    }
}

class ShouldITestPrivateMethods(
    private val differenceCalculator: DifferenceCalculator
) {
    fun processSomething(input: Int): String {
        return if (input >= CONSTANT_NUMBER) {
            "Number is greater or equal $CONSTANT_NUMBER"
        } else {
            "You need to add ${calculateDifference(input)} to get $CONSTANT_NUMBER"
        }
    }

    fun calculateDifference(input: Int) = differenceCalculator.differenceBetween(
        first = CONSTANT_NUMBER,
        second = input
    )

    companion object {
        private const val CONSTANT_NUMBER = 420
    }
}

To sum things up:

  • test private behavior in context of public accessors
  • refactor private behavior - extract it to higher component
  • don't just change private modifier to public to test code

And last, but not least, don't make private method public. Your future self will thank you for keeping strict property access.

Do you use verifyNoMoreInteractions?

tl;dr In unit tests - almost never. In integrations tests - sometimes.

Let's take a look at javadoc for Mockito.verifyNoMoreInteractions:

/**
* Checks if any of given mocks has any unverified interaction.
* <p>
* You can use this method after you verified your mocks - to make sure that nothing
* else was invoked on your mocks.
**/

Where it could be useful? Lets take a look at example.

import java.util.function.Consumer

class System(
    private val consumer: Consumer<Any>
) : AutoCloseable {

    var shouldAccept = true

    fun ping() {
        if (shouldAccept) {
            consumer.accept(Any())
        }
    }

    override fun close() {
        shouldAccept = false
    }
}

Now we'd like to assert that consumer won't accept value if system was already closed:

import com.nhaarman.mockitokotlin2.*
import org.junit.jupiter.api.Test

class Verifications {
    @Test
    fun checkVerifications() {
        val mockConsumer = mock<Consumer<Any>>()
        val system = System(consumer = mockConsumer)

        system.ping()
        system.ping()

        verify(mockConsumer, times(2)).accept(any())

        system.close()
        system.ping()

        verifyNoMoreInteractions(mockConsumer)
    }
}

We're checking that after system creation and two ping() method invocations, consumer accepted two values. Then, we close system and make one more extra assertion: no more interactions on mock.

We can refactor this test method to check the same behavior without using noMoreInteractions:

class Verifications {
    @Test
    fun checkVerifications() {
        val mockConsumer = mock<Consumer<Any>>()
        val system = System(consumer = mockConsumer)

        system.ping()
        system.ping()

        system.close()
        system.ping()

        verify(mockConsumer, times(2)).accept(any())
    }
}

And now we have one assertion at the end of the test.

Is using verifyNoMoreInteractions worth it? Maybe it is, for work-in-progress tests when you are not sure how to approach given test scenario or for more complex integrations test when you have to be sure that nothing else happened on given mock. But I'm sure that in unit test scenario there are better tools to do the same job.

Should Android developers exchange Mockito with MockK?

tl;dr: it's up to your preferences and your coding style

I would rephrase this question to should Kotlin developers exchange Mockito with MockK, since most of new native Android projects are written in Kotlin.

For new projects: absolutely yes. Mockk gives you first-class Kotlin support (such as mocking suspend functions).

You don't have to rewrite all your test doubles from Mockito to MockK - it is possible to use together even within the same test class, but for the sake of readability, use one mocking framework for one test class. If you just adding new test cases to existing class - use mocking framework that was used there before.

How about using Mockito-Kotlin instead of regular Mockito in Kotlin projects?

tl;dr It's the way.

For rewriting regular Mockito test case to Mockito-Kotlin DSL check this article:

Using Mockito in Kotlin projects
Refactoring Java Mockito based test suite to Mockito-Kotlin DSL

How to approach building test suite for project that has no tests at all?

tl;dr: start with integration tests and once they are passing, refactor and write unit tests

That surely depends on complexity of your project. If there are no test at all, you would focus on testing components that has highest business value. You may start here with integration or E2E tests.

For backend project - you can start with testing REST controller -  you setup in-memory database, setup rest of Spring context and just invoke controllers method with given set of params.

For backend projects in Ktor - check this article:

Testing Ktor Controllers
Functional tests in Ktor - how to mock application server with TestApplicationEngine?

For mobile projects - you may record test cases with Espresso recorder or start with integration test for presentation layer.

If you have happy few paths tested - you may start with refactoring code to clean architecture approach. Once SOLID, or at least dependency inversion principle are introduced, create unit test.  

But what if I'm not sure which components has the highest business value? Check this tool. It will give you some insights about code based on git repository history, including which files were edited most frequently and which files where changed together with others.

CodeScene
CodeScene by Empear - The history of your code will decide its future.

When to use mock, and when to use fake?

In this context we call mock a test double generated by framework such as Mockito or MockK, and fake - lightweight implementation of system under test dependency.

That surely depends on your test case. For stubbing - go with fake. If your test case need using side effect verification - use mock.

General rules I try to follow when deciding if I should go with fake or with mock:

  • if I have single method interface - fake
  • if I have to throw exception inside method - fake
  • if I have to match specific argument - mock + matchers
  • if I have to create test double for larger component and I don't feel like implementing all methods - mock

Or in simpler words - choose faster method.


Fake it till you make it - effective use of test doubles

Thanks again for inviting me to Droidcon series and Devfest Poland - it was great pleasure to be among great speakers! Hopefully we will come back to offline conferences quickly.

Fake it till you make it - effective use of test doubles
Test doubles are powerful tool in developer trying to write unit tests. Mocks allow us to keep system under test isolated so we can check given scenarios and perform assertions on singular piece of logic with ease. In this talk I will dive into fakes, mocks and other test doubles and I will present …
Droidcon APAC, December 2020
Fake it till you make it - effective use of test doubles
Test doubles are powerful tool in developer trying to write unit tests. Mocks allow us to keep system under test isolated so we can check given scenarios and perform assertions on singular piece of logic with ease. In this talk I will dive into fakes, mocks and other test doubles and I will present …
Droidcon Americas, November 2020
Devfest Poland, October 2020
Jarosław Michalik

Jarosław Michalik

Working on mobile applications in Poland. Speaking at conferences and meetups about code quality and unit testing tools, blogging about tech.