简体   繁体   English

Mockito/JMockit & Hamcrest 匹配器:如何验证列表/集合?

[英]Mockito/JMockit & Hamcrest matchers : How to verify Lists/Collections?

This 2013 post on SO asked how to use Hamcrest matchers to verify lists/collections invocations in Mockito.这篇2013 年关于 SO 的帖子询问了如何使用 Hamcrest 匹配器来验证 Mockito 中的列表/集合调用。 The accepted solution was to cast the Matcher to a (Collection).公认的解决方案是将 Matcher 转换为 (Collection)。

I'm trying to do something similar, but running into a class cast error.我正在尝试做类似的事情,但遇到了类转换错误。 I am not sure if I am misusing Hamcrest matchers, or if this usage simply isn't supported by Mockito.我不确定我是否误用了 Hamcrest 匹配器,或者 Mockito 是否根本不支持这种用法。 In my case, I'm trying to use a list of Matchers as my argument:就我而言,我试图使用匹配器列表作为我的参数:

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")))));
}

But I get the cast error:但我得到了演员错误:

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

Am I doing something unsupported?我在做一些不受支持的事情吗?

I would prefer use allOf我更喜欢使用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"))
            )
        )
    );

As Jeff Bowman has already pointed out, the problem is that the compiler doesn't know which of the 4 contains methods you are trying to call.正如 Jeff Bowman 已经指出的那样,问题在于编译器不知道 4 个contains您尝试调用的方法中的哪一个。

The list you are constructing您正在构建的列表

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

is of type是类型

List<Matcher<String>>

but the contains method you want to call ( <E> Matcher<Iterable<? extends E>> contains(List<Matcher<? super E>> itemMatchers) ) expects a type但是您要调用的contains方法( <E> Matcher<Iterable<? extends E>> contains(List<Matcher<? super E>> itemMatchers) )需要一个类型

List<Matcher<? super String>>

as parameter.作为参数。 As your list type doesn't match the expected one, the compiler actually thinks that you are trying to call由于您的列表类型与预期类型不匹配,编译器实际上认为您正在尝试调用

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

The solution: give the compiler what it wants.解决方案:给编译器它想要的东西。 Create a List<Matcher<? super String>>创建一个List<Matcher<? super String>> List<Matcher<? super String>> instead of a List<Matcher<String>> : List<Matcher<? super String>>而不是List<Matcher<String>>

        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)));

EDIT:编辑:

Adding Jeff Bowman's inline solution from his comment, that enables the use of Arrays.asList as stated in the question:从他的评论中添加 Jeff Bowman 的内联解决方案,这使得可以使用问题中所述的Arrays.asList

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

I believe this is due to an annoying ambiguity in Hamcrest, which has on its Matchers class :我相信这是由于 Hamcrest 中令人讨厌的歧义,它的Matchers 类

  1. <E> Matcher<Iterable<? extends E>> contains(E... items)
  2. <E> Matcher<Iterable<? extends E>> contains(Matcher<? super E> itemMatcher)
  3. <E> Matcher<Iterable<? extends E>> contains(Matcher<? super E>... itemMatchers)
  4. <E> Matcher<Iterable<? extends E>> contains(List<Matcher<? super E>> itemMatchers)

That's right, depending on whether you pass Hamcrest an item, a matcher, a varargs array of matchers, or a list of matchers, you'll get different behavior.没错,根据您是向 Hamcrest 传递项目、匹配器、匹配器的可变参数数组还是匹配器列表,您将获得不同的行为。 Because Java has no aversion to matching lists of Hamcrest matchers, there is plenty of opportunity for one statement to match more than one of those overloads, and the choice between them is the most specific overload as determined by dizzying type algebra in JLS 18.5.4 .因为 Java 不反对匹配 Hamcrest 匹配器列表,所以一个语句有很多机会匹配多个重载,并且它们之间的选择是最具体的重载,由JLS 18.5.4 中令人眼花缭乱的类型代数确定.

I think you're intending item #4 above—pass contains a List of Matcher<E> ( Matcher<String> ) and get back a Matcher<Iterable<? extends String>>我认为您打算使用上面的第 4 项Matcher<E> contains一个Matcher<E> ( Matcher<String> ) 列表并返回一个Matcher<Iterable<? extends String>> Matcher<Iterable<? extends String>> —but the compiler sees it as #1—pass contains a value of type E ( List<Matcher<String>> ) and get back a Matcher<Iterable<? extends List<Matcher<String>>>> Matcher<Iterable<? extends String>> -但是编译器将其视为#1通contains类型的EList<Matcher<String>> ),并得到一个Matcher<Iterable<? extends List<Matcher<String>>>> Matcher<Iterable<? extends List<Matcher<String>>>> . Matcher<Iterable<? extends List<Matcher<String>>>>

There are a couple workarounds, which I haven't tested yet :有几种解决方法,我还没有测试过

  • Extract the Matcher to a variable, which you can do with Hamcrest matchers like contains but not Mockito matchers like argThat :Matcher提取到一个变量中,您可以使用 Hamcrest 匹配器(如contains而不是 Mockito 匹配器(如argThat

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

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

The best way is to use the standard assertThat method (from Hamcrest or JUnit), which will work best with any Hamcrest matcher.最好的方法是使用标准的assertThat方法(来自 Hamcrest 或 JUnit),它最适合任何 Hamcrest 匹配器。 With JMockit you could then do:使用 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.
    }};
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM