Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why Mockito can't mock a generic parameter type with number type in Kotlin?

We're moving our project to the Kotlin language. We decided to start from tests but faced with some strange behavior.

Here is our test case:

Service.java

public final class Service {
    private final JdbcTemplate jdbcTemplate;

    public Service(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public long check() {
        return jdbcTemplate.queryForObject("SELECT COUNT(*) FROM table", Long.class);
    }
}

JavaTest.java (works fine)

@RunWith(MockitoJUnitRunner.class)
public final class JavaTest {
    @Mock
    private JdbcTemplate jdbcTemplate;

    @InjectMocks
    private Service testSubject;

    @Test
    public void test() {
        //given
        when(jdbcTemplate.queryForObject(anyString(), eq(Long.class))).thenReturn(1L);

        //when
        long result = testSubject.check();

        //then
        assertThat(result, is(1L));
    }
}

KotlinTest.kt (not working)

@RunWith(MockitoJUnitRunner::class)
class KotlinTest {
    @Mock
    private lateinit var jdbcTemplate: JdbcTemplate

    @InjectMocks
    private lateinit var testSubject: Service

    @Test
    fun test() {
        //given
        `when`(jdbcTemplate.queryForObject(anyString(), eq(Long::class.java))).thenReturn(1L)

        //when
        val result = testSubject.check()

        //then
        assertThat(result, `is`(1L))
    }
}

Kotlin test fails with NullPointerException:

java.lang.NullPointerException
    at c.i.Service.check(Service.java:13)
    at c.i.KotlinTest.test(KotlinTest.kt:30)

Also, MockitoHint says:

[MockitoHint] KotlinTest.test (see javadoc for MockitoHint):
[MockitoHint] 1. Unused... -> at org.springframework.jdbc.core.JdbcTemplate.queryForObject(JdbcTemplate.java:500)
[MockitoHint]  ...args ok? -> at org.springframework.jdbc.core.JdbcTemplate.queryForObject(JdbcTemplate.java:500)

Can someone describe what is happening here? I'm quite new to Kotlin and might miss something.

Dependencies version: Kotlin 1.1.3-2, Mockito 2.7.19

like image 920
ninja Avatar asked Jul 18 '17 16:07

ninja


1 Answers

Please use the KClass#javaObjectType instead, for example:

// use java.lang.Long rather than long ---v
when(jdbcTemplate.queryForObject(anyString(), eq(Long::class.javaObjectType)))
                   .thenReturn(1L)

Why does this error occurs?

This is because Long::class.java returns a primitive type long class rather than a java.lang.Long class. For example:

println(Long::class.java.name) // long
println(Long::class.javaObjectType.name) // java.lang.Long

println(Long::class.javaObjectType == Long::class.java) 
//                                 ^--- false: their class are different

The mocked method parameters matcher is [String, Class<long>] in the Kotlin test code. When mockito can not find the matched method [String, Class<Long>] for mocking in Java Service class, then it will return a default value for the mismatched getForObject method call, but the return type of the getForObject method is Object - so a null value is returned by default.

However, the return type of the check method is long, and the JVM tries unboxing null into the primitive type long in your Service class - which causes a NullPointerException to be thrown. For example:

when(jdbcTemplate.queryForObject(anyString(), eq(Long::class.java)))
                   .thenReturn(1L)

assertEquals(1, jdbcTemplate.queryForObject("<any>", Long::class.java))  
//                           ^--- matched: return 1 

assertNull(jdbcTemplate.queryForObject("<any>", Long::class.javaObjectType)) 
//                      ^--- mismatched: return null

testSubject.check()
//          ^--- throws NullPointerException

IF you will replace the usage of the Long class with long.class - you will also get the same error. For example:

//      use long.class rather than Long.class ---v
when(jdbcTemplate.queryForObject(anyString(), eq(long.class))).thenReturn(1L);


 //                      v--- matched: return 1L
assertThat(jdbcTemplate.queryForObject("<any>", long.class), is(1L));

try {
    //                        v--- mismatched: return null
    long value = jdbcTemplate.queryForObject("<any>", Long.class);
    //   ^--- throws NullPointerException when doing unboxing operation  
    fail();
} catch (NullPointerException expected) {
    assertTrue(true);
}
like image 184
holi-java Avatar answered Sep 23 '22 13:09

holi-java