【问题标题】:Mockito error with custom matcher自定义匹配器的 Mockito 错误
【发布时间】:2016-06-15 22:29:50
【问题描述】:

我有一个 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 of a given size 和 Mockito 文档中所示。我得到了同样的错误。
  2. 我尝试使用看起来像这样的 Hamcrest 匹配器,但尽管 1.3 Javadocs 列出了 Matchers.hasSize() 方法,但我导入的 1.3 jar 不包含 Matchers。 (但即使我解决了依赖关系,我仍然想了解问题。)

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

谢谢

【问题讨论】:

  • 如果 matches() 检查 null 并为 null 参数返回 false 会发生什么(IMO 应该这样做)
  • 我使用的是 Mockito 文档中的示例(基本上),它不检查 null。

标签: mocking mockito matcher argument-matcher


【解决方案1】:

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

Matcher 的合约 规定matches(Object) 可以接受任何 对象并返回真或假。这意味着在每一个 Matcher 实现中,你不应该对传入的对象的类型或对象是否为非空做任何假设;毕竟,isNull() 是一个完全有效且有用的匹配器。如果您希望 Matcher 为任何 null 或非 List 参数返回 false,则应手动检查,或扩展 TypeSafeMatcher&lt;List&gt; 而不是 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。这意味着检查现有的存根,因此它将nullargThat 输送到您的Matcher 以查看它是否应该应用您的第一个存根,这会导致NullPointerException。 (关于argThat 的返回值和Mockito 的评估顺序我在another SO answer 中添加了更多信息。)

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

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

...因为在someMethod 之前调用when 意味着Mockito 可以暂时解除您的存根。不过,只要第一行不引发异常或调用真正的实现,将语法保留为 when 并没有什么坏处,Mockito 将优雅地处理验证。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-02-24
    • 2012-08-29
    • 2020-05-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-02
    • 1970-01-01
    相关资源
    最近更新 更多