Mockito错误与自定义匹配器

时间:2016-03-02 21:00:02

标签: mocking mockito matcher argument-matcher

我有一个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,我就不会收到任何错误。所以它不喜欢的是测试的存根部分,以及我使用自定义匹配器两次的事实。

注意:

  1. 我尝试为每个列表大小声明一个单独的类,如mockito anyList of a given size和Mockito文档。我得到了同样的错误。
  2. 我尝试使用看起来像这样的Hamcrest匹配器,但尽管1.3 Javadocs列出了Matchers.hasSize()方法,但我导入的1.3 jar不包含Matchers。 (但即使解决了依赖关系,我仍然希望了解问题。)
  3. 请不要问我为什么使用Mockito而不是Spock Mocks - 我有我的理由。 ;)

    谢谢

1 个答案:

答案 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。这意味着要检查现有的存根,因此它将whennull传输到您的匹配器中,看它是否应该应用您的第一个存根,这会导致argThat。 (我在another SO answer中更多地了解了NullPointerException的返回值和Mockito的评估顺序。)

无论如何你都想修好你的Matcher,但你也可以按照以下方式改写第二行:

argThat

...因为在doReturn([]).when(mock).someMethod(argThat(hasSize(3))) 之前调用when意味着Mockito可以暂时解除你的存根。但是,只要第一行没有抛出异常或调用实际实现,将语法保留为someMethod就没有坏处,Mockito将优雅地处理验证。