我有一个Java类:
import java.util.List;
public class Service
{
public List<Object> someMethod(final List<Object> list) {
return null;
}
}
我在Spock测试中定义了一个自定义匹配器:
导入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而不是Spock Mocks - 我有我的理由。 ;)
谢谢
答案 0 :(得分:0)
根本原因是您的自定义匹配器可能抛出异常,这不符合Matcher的一般合同。由于Mockito的内部原因,你在when
遇到了它。
匹配合同声明matches(Object)
可以接受任何对象并返回true或false。这意味着在每个Matcher实现中,你不应该假设传入的对象的类型,或者对象是否为非null;毕竟,isNull()
是一个完全有效且有用的匹配器。如果您希望Matcher为任何null或非List参数返回false
,您应该手动检查,或者扩展TypeSafeMatcher<List>
而不是BaseMatcher
,以便Hamcrest可以返回{{1}在那些情况下为你服务。否则,您冒着未被捕获的ClassCastException或NullPointerException的风险,这就是您在这里得到的。这是唯一真正的问题,修复它将解决你的麻烦。
这是一个很好的时刻,可以解释 Mockito的语法。第一行没有问题,为什么第二行会失败?答案是当你的第二行运行时:
false
...然后Java评估对when(mock.someMethod(argThat(hasSize(3)))).thenReturn([])
的调用,因此它运行:
when
...然后 mock.someMethod(argThat(hasSize(3)))
可以检测when
作为最后一个被调用的方法并开始其存根。与所有其他Mockito匹配器一样,对someMethod
的调用返回argThat
(将其副作用保留在堆栈中Mockito可以分析Java何时调用null
),但when
已要获得返回值,Mockito无法检测到您将要调用someMethod
。这意味着要检查现有的存根,因此它将when
从null
传输到您的匹配器中,看它是否应该应用您的第一个存根,这会导致argThat
。 (我在another SO answer中更多地了解了NullPointerException
的返回值和Mockito的评估顺序。)
无论如何你都想修好你的Matcher,但你也可以按照以下方式改写第二行:
argThat
...因为在doReturn([]).when(mock).someMethod(argThat(hasSize(3)))
之前调用when
意味着Mockito可以暂时解除你的存根。但是,只要第一行没有抛出异常或调用实际实现,将语法保留为someMethod
就没有坏处,Mockito将优雅地处理验证。