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
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With