'Kotlin Mockito Generics

Suppose my class is:

open class TestThis{
    @Autowired
    private var myService : MyService? = null

    fun doMyFunction(){
        val result = myService.doSomething("hello world", Function { entry ->
                        var retVal : Boolean = false
                        //some processing
                        retVal
                    })
    }
}

@Service
open class MyService{
    fun doSomething(str1 : String, java.util.Function<MyCrap, Boolean>) : List<String>{
        //do something here
    }
}


@RunWith(MockitoJUnitRunner::class)
class TestThisTest{
    @Mock
    var myService : MyService? = null

    @InjectMocks
    var test : TestThis? = null

    @Before
    fun before(){
        val list : List<String> = //init list

        //this line causes compilation error due to generics. error 1
        Mockito.`when`(myService.doSomething(Mockito.anyString(), Mockito.any(Function::class.java))).thenReturn(list)

        //this line also causes compilation error due to generics. error 2
        Mockito.`when`(myService.doSomething(Mockito.anyString(), Mockito.any(Function<MyCrap, Boolean>::class.java))).thenReturn(list)
    }
}

error 1:

Type inference failed. Expected type mismatch.

error 2:

Only classes are allowed on the left hand side of a class literal

So, how do I mock myService#doSomething?



Solution 1:[1]

Getting matchers to work with Kotlin can be a problem.

  • If you have a method written in Kotlin that does not take a nullable parameter then we cannot match with it using Mockito.any(). This is because it returns null and this is not assignable to a non-nullable parameter.
  • in Kotlin classes and members are final by default, we need to open them, because mocking is prohibit for final methods
  • Generic classes mocks rise errors like you described Only classes are allowed on the left hand side of a class literal

All these problems can be resolved via simple MockitoUtils class:

    object MockitoUtils {

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

        inline fun <reified T : Any> createInstance(): T {
            return when (T::class) {
                Boolean::class -> false as T
                Byte::class -> 0.toByte() as T
                Char::class -> 0.toChar() as T
                Short::class -> 0.toShort() as T
                Int::class -> 0 as T
                Long::class -> 0L as T
                Float::class -> 0f as T
                Double::class -> 0.0 as T
                else -> createInstance(T::class)
            }
        }

        fun <T : Any> createInstance(kClass: KClass<T>): T {
            return castNull()
        }

        @Suppress("UNCHECKED_CAST")
        private fun <T> castNull(): T = null as T
    }

Example of usage:

Mockito.`when`(myService!!.doSomething(Mockito.anyString(), MockitoUtils.anyObject())).thenReturn(list)

Service to mock:

open class MyService {

    open fun doSomething(str1 : String, func : java.util.function.Function<MyCrap, Boolean>) : List<String>{
        return emptyList()
    }

}

Service to test:

open class TestThis{
    private var myService : MyService? = null

    fun doMyFunction() : List<String>? {
        val result = myService?.doSomething("hello world", java.util.function.Function { entry ->
            var retVal : Boolean = false
            //some processing
            retVal
        } )
        return result;
    }
}

Test impplementation:

import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.InjectMocks
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.junit.MockitoJUnitRunner
import kotlin.reflect.KClass

@RunWith(MockitoJUnitRunner::class)
class TestThisTest{
    @Mock
    var myService : MyService? = null

    @InjectMocks
    var test : TestThis? = null

    @Before
    fun before(){
        val list : ArrayList<String> = arrayListOf()
        list.add("123")

        val thenReturn = Mockito.`when`(myService!!.doSomething(Mockito.anyString(), MockitoUtils.anyObject())).thenReturn(list)

    }

    @Test
    fun test() {
        val list = test!!.doMyFunction()
        Assert.assertTrue(list!!.contains("123"))
    }

    object MockitoUtils {

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

        inline fun <reified T : Any> createInstance(): T {
            return when (T::class) {
                Boolean::class -> false as T
                Byte::class -> 0.toByte() as T
                Char::class -> 0.toChar() as T
                Short::class -> 0.toShort() as T
                Int::class -> 0 as T
                Long::class -> 0L as T
                Float::class -> 0f as T
                Double::class -> 0.0 as T
                else -> createInstance(T::class)
            }
        }

        fun <T : Any> createInstance(kClass: KClass<T>): T {
            return castNull()
        }

        @Suppress("UNCHECKED_CAST")
        private fun <T> castNull(): T = null as T
    }
}

Alternative solution:
Another possible solution would be to use a library like mockito-kotlin.
Maven:

<dependency>
    <groupId>org.mockito.kotlin</groupId>
    <artifactId>mockito-kotlin</artifactId>
    <version>4.0.0</version>
    <scope>test</scope>
</dependency>

Solution 2:[2]

You should not mock "TestThis" when you try to test something inside this Service. If you mock it there is nothing "inside". It is just a mock. Try instanciating it and then inject a mock of MyService.

It also seems weird where you are writing your Test. Why is a Unit test for MyService in the testclass TestThisTest. You should creat your own Unit test for MyService.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Eugene
Solution 2 GJohannes