【问题标题】:Mockito/JMockit & Hamcrest matchers : How to verify Lists/Collections?Mockito/JMockit & Hamcrest 匹配器:如何验证列表/集合?
【发布时间】:2015-10-26 17:05:08
【问题描述】:

2013 post on SO 询问如何使用 Hamcrest 匹配器来验证 Mockito 中的列表/集合调用。公认的解决方案是将 Matcher 转换为 (Collection)。

我正在尝试做类似的事情,但遇到了类转换错误。我不确定我是否在滥用 Hamcrest 匹配器,或者 Mockito 是否根本不支持这种用法。就我而言,我正在尝试使用匹配器列表作为我的论点:

static class Collaborator
{
   void doSomething(Iterable<String> values) {}
}

@Test
public void usingMockito()
{
   Collaborator mock = Mockito.mock(Collaborator.class);
   mock.doSomething(Arrays.asList("a", "b"));

   // legal cast
   Mockito.verify(mock).doSomething((Collection<String>)argThat(Matchers.contains("a", "b")));
   // legal cast
   Mockito.verify(mock).doSomething((Collection<String>)argThat(Matchers.contains(Matchers.equalTo("a"), Matchers.equalTo("b"))));

   // illegal cast!!! Cannot cast from Iterable<capture#3-of ? extends List<Matcher<String>>> to Collection<String>
   Mockito.verify(mock).doSomething((Collection<String>)argThat(Matchers.contains(Arrays.asList(Matchers.equalTo("a"), Matchers.equalTo("b")))));
}

但我得到了转换错误:

Cannot cast from Iterable<capture#3-of ? extends List<Matcher<String>>> to Collection<String>

我在做一些不受支持的事情吗?

【问题讨论】:

    标签: java generics mockito jmockit hamcrest


    【解决方案1】:

    我更喜欢使用allOf

    import static org.hamcrest.Matchers.allOf;
    import static org.hamcrest.Matchers.equalTo;
    import static org.hamcrest.Matchers.hasItems;
    import static org.hamcrest.Matchers.hasProperty;
    import static org.hamcrest.Matchers.hasSize;
    import static org.hamcrest.Matchers.notNullValue;
    import static org.hamcrest.Matchers.nullValue;
    
    ...
    
        Mockito.verify(mock).doSomething(
            argThat(
                allOf(
                    hasItems(equalTo("a")),
                    hasItems(equalTo("b"))
                )
            )
        );
    

    【讨论】:

      【解决方案2】:

      正如 Jeff Bowman 已经指出的那样,问题在于编译器不知道您尝试调用的 4 个 contains 方法中的哪一个。

      你正在构建的列表

      Arrays.asList(Matchers.equalTo("a"), Matchers.equalTo("b"))
      

      是类型

      List<Matcher<String>>
      

      但是您要调用的 contains 方法 (&lt;E&gt; Matcher&lt;Iterable&lt;? extends E&gt;&gt; contains(List&lt;Matcher&lt;? super E&gt;&gt; itemMatchers)) 需要一个类型

      List<Matcher<? super String>>
      

      作为参数。由于您的列表类型与预期的不匹配,编译器实际上认为您正在尝试调用

      <E> Matcher<Iterable<? extends E>> contains(E... items)
      

      解决方案:给编译器它想要的东西。创建一个List&lt;Matcher&lt;? super String&gt;&gt; 而不是List&lt;Matcher&lt;String&gt;&gt;

              List<Matcher<? super String>> matchersList = new ArrayList<>();
              matchersList.add(Matchers.equalTo("a"));
              matchersList.add(Matchers.equalTo("b"));
      
              // no illegal cast anymore
              Mockito.verify(mock).doSomething(
                  (Collection<String>) argThat(Matchers.contains(matchersList)));
      

      编辑:

      从他的评论中添加 Jeff Bowman 的内联解决方案,这样可以使用Arrays.asList,如问题中所述:

      Mockito.verify(mock).doSomething(
         (Collection<String>) argThat(
              Matchers.contains(
                  Arrays.<Matcher<? super String>> asList(
                      Matchers.equalTo("a"), Matchers.equalTo("b")
                  )
              )
          )
      );
      

      【讨论】:

      • 感谢您的关注! +1,您也可以通过参数化asList(即Arrays.&lt;? super String&gt;&gt;asList(...))来内联执行此操作。
      • @JeffBowman 很棒。我会把它添加到答案中,希望你不介意
      • 请做!我没有机会对其进行测试,但是您通过非常具体的解释阐明了一个非常好的答案,并且绝对应该为此获得应有的赞誉。
      • 重载不是原因;请参阅我对杰夫的回答的评论。
      • 请注意,在较新版本的Mockito MockitHamcrest#argThat 中应该使用Mockito#argThat 而不是Mockito#argThat
      【解决方案3】:

      我相信这是由于 Hamcrest 中令人讨厌的模棱两可,它的 Matchers class

      1. &lt;E&gt; Matcher&lt;Iterable&lt;? extends E&gt;&gt; contains(E... items)
      2. &lt;E&gt; Matcher&lt;Iterable&lt;? extends E&gt;&gt; contains(Matcher&lt;? super E&gt; itemMatcher)
      3. &lt;E&gt; Matcher&lt;Iterable&lt;? extends E&gt;&gt; contains(Matcher&lt;? super E&gt;... itemMatchers)
      4. &lt;E&gt; Matcher&lt;Iterable&lt;? extends E&gt;&gt; contains(List&lt;Matcher&lt;? super E&gt;&gt; itemMatchers)

      没错,取决于你传递 Hamcrest 一个项目、一个匹配器、一个可变参数数组还是一个匹配器列表,你会得到不同的行为。因为 Java 不反对 Hamcrest 匹配器的匹配列表,所以一个语句有很多机会匹配多个这些重载,并且它们之间的选择是由 JLS 18.5.4 中令人眼花缭乱的类型代数确定的最具体的重载。

      我认为您打算上面的第 4 项 — 传递 contains 的列表 Matcher&lt;E&gt; (Matcher&lt;String&gt;) 并返回 Matcher&lt;Iterable&lt;? extends String&gt;&gt; — 但编译器将其视为 #1 — 传递 contains E (List&lt;Matcher&lt;String&gt;&gt;) 类型的 并返回 Matcher&lt;Iterable&lt;? extends List&lt;Matcher&lt;String&gt;&gt;&gt;&gt;

      有几个解决方法,我还没有测试过

      • Matcher 提取到一个变量中,您可以使用像contains 这样的Hamcrest 匹配器,但不能使用像argThat 这样的Mockito 匹配器:

        Matcher<Iterable<String>> matchesAAndB = Matchers.contains(
            Arrays.asList(Matchers.equalTo("a"), Matchers.equalTo("b")));
        Mockito.verify(mock).doSomething((Collection<String>)argThat(matchesAAndB));
        
      • 明确选择 E:

        Mockito.verify(mock).doSomething((Collection<String>)argThat(
            Matchers.<String>contains(Arrays.asList(
                Matchers.equalTo("a"), Matchers.equalTo("b")))));
        

      【讨论】:

      • 哇...我不会想到的。由于重载问题,我没有意识到编译器会丢失这个。我将进行一些快速测试并找出正确的说法。谢谢。
      • 事实证明,超载不是问题的原因。我们可以看到,通过向测试类添加两个虚拟方法,并使用它们而不是 Hamcrest 匹配器:static &lt;T&gt; Matcher&lt;Iterable&lt;T&gt;&gt; hasItems(T... items) { return null; }static &lt;T&gt; Matcher&lt;Iterable&lt;? extends T&gt;&gt; contains(T... items) { return null; },其中将第一个与 argThat 一起使用可以正常编译,而第二个则不行。真正的原因是在第二个匹配器中使用了? extends T
      • 是的,这是一个很好的总结:不兼容的有界泛型是根本原因。请阅读 OP 的错误消息,您就会明白为什么重载是症状中令人困惑的部分。
      【解决方案4】:

      最好的方法是使用标准的assertThat 方法(来自Hamcrest 或JUnit),它最适合任何Hamcrest 匹配器。使用 JMockit,您可以这样做:

      @Test
      public void usingJMockit(@Mocked final Collaborator mock) {
          mock.doSomething(asList("a", "b"));
      
          new Verifications() {{
              List<String> values;
              mock.doSomething(values = withCapture());
      
              // Now check the list of captured values using JUnit/Hamcrest:
              assertThat(values, contains("a", "b"));
      
              // Alternatively, could have used Asser4J, FEST Assert, etc.
          }};
      }
      

      【讨论】:

      • Mockito 类似物是ArgumentCaptor,对于它的通用捕获可以用@Captor 更容易地表达。请注意,您可能必须更加努力地忽略不匹配的方法调用,就像 verify 自动执行的那样。
      • @JeffBowman JMockit 的 withCapture() 相当于 Mockito 的 ArgumentCaptor,我相信;它们都支持泛型类型。不过,我不确定您所说的“更加努力地忽略不匹配的方法调用”是什么意思;这两个 API 不也一样吗?
      • 这个问题有一个 Mockito 标签,而不是 JMockit,所以为了提问者的缘故,我提供了等价的标签。我想这两种捕获解决方案是相同的,但是与将 Matcher 传递给 Mockito 的解决方案相比,任何捕获解决方案都会变得复杂当对同一方法进行多次调用时 .然后你的捕获者将有多个值要检查,而不仅仅是一个,并且你需要断言它们中的 any 匹配。
      • @JeffBowman 问题的标题是“Mockito/JMockit”(现在它有“jmockit”标签)。现在,当然使用参数捕获不允许测试限制它将匹配哪些调用,但是 Mockito 和 JMockit 的“限制”是相同的。因此,“我的俘虏”与 Mockito 测试所拥有的没有区别;这就是我要说的。
      猜你喜欢
      • 1970-01-01
      • 2015-09-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多