Kotlin Mockito Generics

919 views Asked by At

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?

2

There are 2 answers

1
GJohannes On

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.

0
Eugene On

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

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>