Gradle, JUpiter, Kotlin and Mockito aboard the same boat

5 minute read

Introduction

When you create a new Android project, Android Studio generates the boilerplate you need to quickstart it. It generates the first screen files, the Gradle configuration and some tests based on JUnit4. Today we will migrate these tests to the new JUnit platform with JUpiter, the new JUnit API.

Because everyone in the Android sphere is talking about Kotlin, we can’t give examples in Java code anymore. So, in this post we will use this tools:

Of course, I based this post on an Android project, but almost all this tips and tricks can be applied to others frameworks.

Add JUnit5 platform to the project

First of all, add the JUnit5 plugin to your root build.gradle.

classpath "de.mannodermaus.gradle.plugins:android-junit5:1.0.31"

Add dependencies to your module’s build.gradle:

apply plugin: 'de.mannodermaus.android-junit5'

dependencies {
    // Remove this line!
    testImplementation 'junit:junit:4.12'

    // Required
    testImplementation junit5.unitTests()

    // Optional
    testImplementation junit5.parameterized()

    // Android
    androidTestImplementation junit5.instrumentationTests()
}

To start, write a test

Some annotations have been renamed by JUpiter, but @Test stays @Test within a new package:

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test

class SimpleUnitTest {
    @Test
    fun testAddition() {
        assertEquals(4, 2 + 2)
    }
}

This test class will produce the following output, nothing new yet.

JUnit5 test result

Parametrized tests

Parametrized tests are now JUnit natives, one way to use them is to provide inputs through a method:

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import java.util.stream.Stream

@DisplayName("Tests on calculator")
class SimpleUnitTest {

    @DisplayName("Check if addition works")
    @MethodSource("calculationSource")
    @ParameterizedTest(name = "Adding {0} and {1} should give you {2}")
    fun whateverTheName(a: Int, b: Int, result: Int) {
        assertEquals(result, a + b)
    }

    companion object {
        @JvmStatic
        fun calculationSource(): Stream<Arguments> {
            return Stream.of(
                    Arguments.of(2, 2, 4),
                    Arguments.of(1, 5, 6)
            )
        }
    }
}

Parametrized test result

@DisplayName("Some human readable text") can be used either on test classes or test methods to provide a more readable name than the technical name. @MethodSource("calculationSource") sets the name of the method to call to provide test inputs. Take note of @JvmStatic on calculationSource, this method must be a static method of the class. You can also set the wording of each iteration of the test using @ParameterizedTest(name = "Adding {0} and {1} should give you {2}").

Maybe you’ll encouter an issue about Stream.of or Arguments.of that required Java 8 and still use Java 6, your may configure your build.gradle file:

android.kotlinOptions {
    jvmTarget = "1.8"
}

Nested test classes

Nested test classes can be really useful to group test methods. For instance, say we have test methods that require the same kind of init before executing the code, on JUnit4 we used @Before that apply on every methods, on Jupiter we can use @BeforeEach on a Kotlin inner class annotated @Nested. The setup will be executed before each methods within the nested test class.

@DisplayName("Tests on calculator")
class NestedExample {

    @Nested
    @DisplayName("Adding to 0 return")
    inner class AddingToZero {

        @BeforeEach
        fun setup() {
            // Given
            // ...
        }

        @ParameterizedTest(name = "Adding {0} to zero should give you {0}")
        @DisplayName("Check if addition works")
        @ValueSource(ints = [2, 4, 5])
        fun whateverTheName(a: Int) {
            // When
            val addition = 0 + a

            // Then
            assertEquals(a, addition)
        }

    }

    @Nested
    @DisplayName("Another init")
    inner class AnotherInitNeeded {
        // ...
    }

}

In Kotlin, inner classes can’t have a companion object. When you need @MethodSource within an inner class, you must change the lifecycle of the nested test class:

@DisplayName("Tests on calculator")
class NestedExample {

    @Nested
    @DisplayName("Adding two number")
    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
    inner class AddingTwoNumbers {

        @ParameterizedTest(name = "Adding {0} to {1} should give you {2}")
        @DisplayName("Check if addition works")
        @MethodSource("calculationSource")
        fun whateverTheName(a: Int, b: Int, result: Int) {
            assertEquals(result, a + b)
        }

        fun calculationSource(): Stream<Arguments> {
            return Stream.of(
                    Arguments.of(2, 2, 4),
                    Arguments.of(1, 5, 6)
            )
        }
    }

}

@TestInstance(TestInstance.Lifecycle.PER_CLASS) tells JUnit5 to start an instance only once for all the methods of AddingTwoNumbers class, instead of the default value PER_METHOD. And now, the calculationSource() method is not part of a companion anymore.

Mockito VS Kotlin

One of the biggest principles of Kotlin is having closed classes by default. It means that, by default, you cannot subclass any classes. You will have to declare the super-class as open, to explicitly say that it is subclassable.

class A
class B: A() <= Compilation error

open class C
class D : C() <= OK

Why is this an issue? Because Mockito uses this mecanism to create mocks. I don’t know what’s your idea about it, but I won’t open all my classes just to make unit tests.

Hopefully, since version 2.1.0, Mockito offers a way to mock our Kotlin classes and methods without modifying our code (NB: It also applies for Java final classes and final methods). Please give a look to Mockito’s Javadoc about Mocking final.

Rules became Extensions

JUnit rules no longer exist in Jupiter, we now use the Jupiter Extensions. I didn’t find yet any official extensions for Mockito, but JUnit team proposed an implementation.

Here is my Kotlin version of the MockitoExtension:

import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.extension.ExtensionContext.Namespace
import org.junit.jupiter.api.extension.ParameterContext
import org.junit.jupiter.api.extension.ParameterResolutionException
import org.junit.jupiter.api.extension.ParameterResolver
import org.junit.jupiter.api.extension.TestInstancePostProcessor
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
import java.lang.reflect.Parameter

class MockitoExtension : TestInstancePostProcessor, ParameterResolver {

    override fun postProcessTestInstance(testInstance: Any, context: ExtensionContext) {
        MockitoAnnotations.initMocks(testInstance)
    }

    @Throws(ParameterResolutionException::class)
    override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean =
            parameterContext.parameter.isAnnotationPresent(Mock::class.java)

    @Throws(ParameterResolutionException::class)
    override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Any =
            getMock(parameterContext.parameter, extensionContext)

    private fun getMock(parameter: Parameter, extensionContext: ExtensionContext): Any {
        val mockType = parameter.type
        val mocks = extensionContext.getStore(Namespace.create(MockitoExtension::class.java, mockType))
        val mockName = getMockName(parameter)

        return if (mockName != null) {
            mocks.getOrComputeIfAbsent<String, Any>(mockName) { _ -> mock(mockType, mockName) }
        } else {
            mocks.getOrComputeIfAbsent<String, Any>(mockType.canonicalName) { _ -> mock(mockType) }
        }
    }

    private fun getMockName(parameter: Parameter): String? {
        val explicitMockName = parameter.getAnnotation(Mock::class.java).name.trim { it <= ' ' }
        return when {
            !explicitMockName.isEmpty() -> explicitMockName
            parameter.isNamePresent -> parameter.name
            else -> null
        }
    }
}

And use the @ExtendWith to apply it on your tests:

class TheService {
    fun getIt() : Int = 42
}
class TheSuv(val service: TheService) {
    fun getItTwice() = service.getIt() * 2
}
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.BDDMockito.given
import org.mockito.InjectMocks
import org.mockito.Mock
import org.mockito.Mockito.verify

@ExtendWith(MockitoExtension::class)
class MockitoExample {

    @Mock
    lateinit var theService: TheService

    @InjectMocks
    lateinit var theSuv: TheSuv

    @Test
    fun testIt() {
        // Given
        given(theService.getIt()).willReturn(13)

        // When
        val twice = theSuv.getItTwice()

        // Then
        verify(theService).getIt()
        assertEquals(26, twice)
    }

}

Still any() issue?

You may want to use the mocked method any() from Mockito, but will face this error: java.lang.IllegalStateException: any() must not be null. I invite you to use the nhaarman/mockito-kotlin library, it provides workarounds for this kind of call.

For instance (from Mockito-kotlin 2.x implementation):

inline fun <reified T : Any> any(): T {
    return Mockito.any(T::class.java) ?: createInstance()
}

Conclusion

As you see it is not that easy to make all this tools to work together, but it is possbile. And I hope I conviced you to move from JUnit4 to JUnit5+Jupiter.

We haven’t talked about all the features available in the Jupiter’s API, there are a lot of nice posts about it.

About Mockito, you will maybe want to look at alternatives, you should start with Mockk. It is quite new, however it is a really good alternative to Mockito for Kotlin development.


Written by

Olivier Perez

Software craftsman, lead developer. #android #kotlin #git