Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mockito's argThat returning null when in Kotlin

Given the following class (written in kotlin):

class Target {
     fun <R> target(filter: String, mapper: (String) -> R): R = mapper(filter)
}

I'm able to test in java, the test code:

@Test
public void testInJava() {
    Target mockTarget = Mockito.mock(Target.class);
    Mockito.when(mockTarget.target(
            argThat(it -> true),
            Mockito.argThat(it -> true)
    )).thenReturn(100);
    assert mockTarget.target("Hello World", it -> 1) == 100;
}

The java test pass as expected, but the same test is written in kotlin:

@Test
fun test() {
    val mockTarget = Mockito.mock(Target::class.java)
    Mockito.`when`(mockTarget.target(
            Mockito.argThat<String> { true },
            mapper = Mockito.argThat<Function1<String, Int>>({ true }))
    ).thenReturn(100)
    assert(mockTarget.target("Hello World") { 1 } == 100)
}

The kotlin version I receive the following exception:

java.lang.IllegalStateException: Mockito.argThat<String> { true } must not be null

Why is it happening and how can I test that using kotlin?

like image 977
ademar111190 Avatar asked Sep 18 '18 15:09

ademar111190


3 Answers

In 2022, Mockito-Kotlin officially solves the problem.

The fix is very simple: Just import the argThat/eq/... from the mockito-kotlin package, instead of the mockito package, and everything is done!

Related: https://github.com/mockito/mockito-kotlin/wiki/Mocking-and-verifying

like image 153
ch271828n Avatar answered Oct 18 '22 01:10

ch271828n


As of this writing, mockito-kotlin hasn't been updated for more than a year. As with all of these libraries, there's always a constant need for keeping them up-to-date, and I didn't want to get stuck with an unmaintained library.

So I came up with another way to solve the null issue with argThat without using any other libraries.

Say we've an interface UuidRepository as follows:

interface UuidRepository {
    suspend fun Entity save(entity: Entity): Entity
}

class Entity has two properties, userId: String and uuid: String.

The following code fails:

Mockito.verify(uuidRepository).save(argThat { it.userId == someValue && it.uuid == "test" })

with the error:

argThat { it.userId == someValue && it.uuid == "test" } must not be null

To solve this, we get all the invocation on the mock and then verify the ones we want:

val invocations = Mockito.mockingDetails(uuidRepository).invocations
    .filter { setOf("findById", "save").contains(it.method.name) }
    .map { it.method.name to it.arguments }
    .toMap()

assertThat(invocations).containsKey("save")
val first = invocations["save"]?.first()
assertThat(first).isNotNull
val entity = first as Entity
assertThat(entity.userId).isEqualTo(someValue)
assertThat(entity.uuid).isEqualTo("test")
like image 31
Abhijit Sarkar Avatar answered Oct 18 '22 00:10

Abhijit Sarkar


I also faced the same problem.

And finally, I found argThat() will return null, and normally the argument in the function in kotlin, does not accept null type.

The source code of argThat from ArgumentMatchers.java

public static <T> T argThat(ArgumentMatcher<T> matcher) {
    reportMatcher(matcher);
    return null;
}

You can see that it return null. So when we mock the function, it will throw IllegalStateException, because argThat returns null and argument can't be null.

It mean that if your function is:

fun doSomething(arg1: String): Int {
    // do something
}

When you mock it like that:

Mockito.`when`(
    doSomething(Mockito.argThat<String> { true })
).thenReturn(100)

It will throw IllegalStateException

So you should change your function like that:

fun doSomething(arg1: String?): Int {
    // do something
}

Change the "String" to "String?", make it accept null type.

My solution is to define the argument with class? so that it can accept null, but I don't know if it is a great solution

like image 2
Infinity_zhang Avatar answered Oct 18 '22 01:10

Infinity_zhang