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



这篇2013年关于SO的文章询问了如何使用Hamcrest匹配器来验证Mockito中的列表/集合调用。可接受的解决方案是将Matcher强制转换为(Collection)。

我正在尝试做类似的事情,但遇到类强制转换错误。我不确定我是否误用了Hamcrest匹配器,或者Mockito根本不支持这种用法。在我的例子中,我试图使用Matchers列表作为我的参数:

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>

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

我更喜欢用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"))
            )
        )
    );

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

您正在构建的列表

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

的类型为

List<Matcher<String>>

但是你想调用的contains方法(<E> Matcher<Iterable<? extends E>> contains(List<Matcher<? super E>> itemMatchers))需要一个类型

List<Matcher<? super String>>

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

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

解决方案:给编译器它想要的。创建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)));
编辑:

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

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

我认为这是由于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)

没错,根据你传递给Hamcrest的是一个项目、一个匹配器、一个匹配器的变量数组,还是一个匹配器的列表,你会得到不同的行为。因为Java不讨厌匹配Hamcrest匹配器列表,所以一条语句有很多机会匹配多个重载,它们之间的选择是由JLS 18.5.4中令人眼花缭乱的类型代数决定的最具体的重载。

我认为你打算上面的项目#4 -通过containsMatcher<E> (Matcher<String>)列表并返回Matcher<Iterable<? extends String>> -但编译器将其视为#1 -通过contains类型E (List<Matcher<String>>)并返回Matcher<Iterable<? extends List<Matcher<String>>>>

有几个变通方法,我还没有测试:

  • 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));
    
  • 显式选择E:

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

最好的方法是使用标准的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.
    }};
}

相关内容

  • 没有找到相关文章