'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 returnsnull
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 |