我有一个Java类:
import java.util.List;
public class Service
{
public List<Object> someMethod(final List<Object> list) {
return null;
}
}
还有一个 Spock 测试,我在其中定义了一个自定义匹配器:
import org.mockito.ArgumentMatcher import spock.lang.Specification
import static org.mockito.Mockito.*
class InstantBookingInitialDecisionTest extends Specification {
def mock = mock(Service.class)
def setup() {
when(mock.someMethod(argThat(hasSize(2)))).thenReturn([])
when(mock.someMethod(argThat(hasSize(3)))).thenReturn([])
}
def 'Minimum hunger requirements do not apply to schedulable pros'() {
when:
'something'
then:
'something else'
}
// Damn, there's a Hamcrest matcher for this, but it's not in the jar that the javadocs say it is, so making my own
static def hasSize(size) {
new ArgumentMatcher<List>() {
@Override
boolean matches(Object o) {
List list = (List) o
return list.size() == size
}
}
}
}
照原样,此测试给我以下错误:
java.lang.NullPointerException: Cannot invoke method size() on null object
如果我删除when
中的任何一个,则不会出错。所以它不喜欢的是测试的存根部分,以及我两次使用自定义匹配器的事实。
笔记:
- 我尝试为每个列表大小声明一个单独的类,就像在给定大小的 mockito anyList 和 Mockito 文档中一样。我得到同样的错误。
- 我尝试使用看起来像这样的Hamcrest匹配器,但是尽管1.3 Javadocs列出了Matchers.hasSize()方法,但我导入的1.3 jar不包括Matchers。(但即使我解决了依赖关系,我仍然想了解这个问题。
请不要问我为什么使用Mockito而不是Spock Mocks - 我有我的理由。 ;)
谢谢
根本原因是自定义匹配器可能会引发异常,这不符合 Matcher 的一般协定。由于Mockito的内部结构,你在when
中遇到了它。
Matcher 的合约规定matches(Object)
可以接受任何对象并返回 true 或 false。这意味着在每个 Matcher 实现中,您不应假设传入对象的类型或对象是否为非 null;毕竟,isNull()
是一个完全有效且有用的匹配器。如果您希望 Matcher 返回任何 null 或非 List 参数的false
,则应手动检查,或扩展TypeSafeMatcher<List>
而不是BaseMatcher
,以便 Hamcrest 可以在这些情况下为您返回false
。否则,您将面临未捕获的ClassCastException或NullPointerException的风险,这就是您在这里得到的。这是这里唯一真正的问题,修复它将解决您的麻烦。
不过,这是解释Mockito语法的好时机。第一行没有问题,为什么第二行会失败呢?答案是,当您的第二行运行时:
when(mock.someMethod(argThat(hasSize(3)))).thenReturn([])
。然后Java计算对when
的调用,因此它运行:
mock.someMethod(argThat(hasSize(3)))
。然后when
可以将someMethod
检测为调用的最后一个方法并开始其存根。与所有其他 Mockito 匹配器一样,对 argThat
的调用返回null
(将其副作用保留在堆栈中,Mockito 可以在稍后调用 Java 时分析when
),但someMethod
必须有一个返回值,并且 Mockito 无法检测到您将要调用when
。这意味着检查现有的存根,以便它将null
从argThat
管道传输到您的 Matcher,以查看它是否应该应用您的第一个存根,这会导致NullPointerException
。(我在另一个 SO 答案中多放了一点关于argThat
的返回值和 Mockito 的评估顺序。
无论如何,您都需要修复您的 Matcher,但您也可以将第二行改写如下:
doReturn([]).when(mock).someMethod(argThat(hasSize(3)))
。因为调用when
之前someMethod
意味着 Mockito 可以暂时解除您的存根。但是,只要第一行不抛出异常或调用实际实现,将语法保留为when
也没有什么坏处,Mockito将优雅地处理验证。