1

我有一个 Java 类:

  import java.util.List;
  public class Service
  {
     public List<Object> someMethod(final List<Object> list) {
        return null;
     }
  }

还有一个 Spock 测试,我在其中定义了一个自定义匹配器:

导入 org.mockito.ArgumentMatcher 导入 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和 Mockito 文档。我犯了同样的错误。
  2. 我尝试使用看起来像这样的 Hamcrest 匹配器,但尽管 1.3 Javadocs 列出了 Matchers.hasSize() 方法,但我导入的 1.3 jar 不包括 Matchers。(但即使我解决了依赖关系,我仍然想了解这个问题。)

请不要问我为什么使用 Mockito 而不是 Spock Mocks - 我有我的理由。;)

谢谢

4

1 回答 1

2

根本原因是您的自定义匹配器可能会抛出异常,这不符合 Matcher 的一般合同。when由于 Mockito 的内部结构,您遇到了它。

Matcher 的合约规定matches(Object)可以接受任何Object 并返回 true 或 false。这意味着在每一个 Matcher 实现中,您都不应该对传入的对象的类型或对象是否为非空做任何假设;毕竟,isNull()它是一个完全有效且有用的匹配器。如果您希望 Matcherfalse为任何 null 或非 List 参数返回,您应该手动检查它,或者扩展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 可以在 Javawhen稍后调用时进行分析),但someMethod必须具有返回值,并且 Mockito 无法检测到您即将调用when. 这意味着检查现有存根,因此它将nullfrom管道argThat传输到您的 Matcher 以查看它是否应该应用您的第一个存根,这会导致NullPointerException. (我在另一个 SO 答案中添加了更多关于argThat' 的返回值和 Mockito 的评估顺序的信息。)

无论如何,您都需要修复您的 Matcher,但您也可以将第二行改写如下:

doReturn([]).when(mock).someMethod(argThat(hasSize(3)))

...因为调用whenbeforesomeMethod意味着 Mockito 可以暂时解除您的存根。但是,只要第一行不抛出异常或调用真正的实现,将语法保留为 没有任何害处when,Mockito 将优雅地处理验证。

于 2016-03-03T00:43:00.380 回答