Mockito - Kotlin测试在试图捕获Pageable参数时抛出空指针异常



我已经写了一个非常简单的测试方法在我的控制器使用Mockito

@Test
fun `get items based on category ID`() {
val pageable: Pageable = PageRequest.of(5, 50)
controller.get(10, pageable)
val captor = ArgumentCaptor.forClass(Int::class.java)
val pageableCaptor = ArgumentCaptor.forClass(Pageable::class.java)
Mockito.verify(itemService).getItemsBasedOnCategoryID(captor.capture(), pageableCaptor.capture())
assertEquals(captor.value, 10)
assertEquals(pageableCaptor.value.pageSize, 50)
assertEquals(pageableCaptor.value.pageNumber, 5)
}

但是我得到了这个异常

pageableCaptor.capture() must not be null
java.lang.NullPointerException: pageableCaptor.capture() must not be null
at com.practice.ItemControllerTest.get items based on category ID(ItemControllerTest.kt:41)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

我无法理解,因为当我使用类似的代码直接在服务层上测试该方法时,它通过了测试。我有这个测试的变通办法,但我只是想了解为什么这是不工作。如果你能帮我,我会很感激的。

如果您还有什么需要我补充的,请随时告诉我。

问题是getItemsBasedOnCategoryIDpageable参数是非空的,而ArgumentCaptor.capture的返回类型是平台类型,这被Kotlin编译器认为可能是空的(实际上capture()返回null,这就是Mockito的工作方式)。在这种情况下,当使用该类型时,编译器将生成空检查。您可以在测试的反编译代码中看到它:

@Test
public final void get_items_based_on_category_ID {
...
Object var10002 = pageableCaptor.capture();
Intrinsics.checkNotNullExpressionValue(var10002, "pageableCaptor.capture()"); <<- NPE
var10000.getItemsBasedOnCategoryID(var4, (Pageable)var10002);
...
}

这个技巧就是以某种方式欺骗编译器,使其不产生空检查。

选项1:使用mockito-kotlin库。它提供了解决这类问题的方法以及一些额外的工具。这可能是你最好的选择,因为你可能会面临下一个问题,例如,当使用Mockito的any()参数匹配器(同样的故事,空与非空不匹配)

选项2: DIY:

  1. 首先,显式声明ArgumentCapture的类型参数为非空的:
val pageableCaptor: ArgumentCaptor<Pageable> = ArgumentCaptor.forClass(Pageable::class.java)

如果没有显式声明,pageableCaptor的类型是ArgumentCaptor<Pageable!>!,即平台类型。

  1. 那么你将需要一个辅助函数:
@Suppress("UNCHECKED_CAST")
private fun <T> capture(captor: ArgumentCaptor<T>): T = captor.capture()

它似乎是一个无操作函数,但关键是它不再返回平台类型:如果ArgumentCaptor的类型参数为非空,则函数返回值的类型也为非空。

  1. 最后使用这个函数代替ArgumentCaptor.capture():
Mockito.verify(itemService).getItemsBasedOnCategoryID(captor.capture(), capture(pageableCaptor))

现在Kotlin编译器认为capture(pageableCaptor)永远不会返回null,所以它不会生成任何null检查。