Mockito和Hamcrest:如何验证Collection参数调用?

6mw9ycah  于 2022-11-08  发布在  其他
关注(0)|答案(7)|浏览(141)

我遇到了一个关于Mockito和Hamcrest的通用问题。
请假设如下界面:

public interface Service {
    void perform(Collection<String> elements);
}

和下面的测试片段:

Service service = mock(Service.class);

// ... perform business logic

verify(service).perform(Matchers.argThat(contains("a", "b")));

因此,我想验证我的业务逻辑实际上调用了一个包含“a”和“b”的集合。
但是,contains(...)的返回类型是Matcher<Iterable<? extends E>>,所以在我的例子中,Matchers.argThat(...)返回Iterable<String>,这自然不适用于所需的Collection<String>
我知道我可以使用Hamcrest hasItem and Mockito verify inconsistency中提出的参数捕获器,但我非常不愿意这样做。
有什么建议吗!谢谢!

0ve6wy6x

0ve6wy6x1#

如果您遇到类似的情况,请记住您可以编写一个非常小的可重用适配器。

verify(service).perform(argThat(isACollectionThat(contains("foo", "bar"))));

private static <T> Matcher<Collection<T>> isACollectionThat(
    final Matcher<Iterable<? extends T>> matcher) {
  return new BaseMatcher<Collection<T>>() {
    @Override public boolean matches(Object item) {
      return matcher.matches(item);
    }

    @Override public void describeTo(Description description) {
      matcher.describeTo(description);
    }
  };
}

请注意,上面的大卫的解决方案,与铸造,是最短的正确答案。

wi3ka0sx

wi3ka0sx2#

你可以把你自己的lambda写成ArgumentMatcher

when(myClass.myMethod(argThat(arg -> arg.containsAll(asList(1,2))))
    .thenReturn(...);
oiopk7p5

oiopk7p53#

假设列表只包含两项,为什么不使用预期的参数进行验证,例如:

final List<String> expected = Lists.newArrayList("a", "b");
verify(service).perform(expected);

虽然我原则上同意Eugen的观点,但我认为依赖于equals进行字符串比较是可以接受的......此外,contains匹配器无论如何都使用equals进行比较。

nhhxz33t

nhhxz33t4#

与这里的另一个答案类似,您可以执行以下操作:

verify(yourmock, times(1)).yourmethod(argThat(arg -> arg.containsAll(asList("a", "b"))));
b91juud3

b91juud35#

您可以拥有自己的java.util.Collection实现并重写equals方法,如下所示。

public interface Service {
    void perform(Collection<String> elements);
}

@Test
public void testName() throws Exception {
    Service service = mock(Service.class);
    service.perform(new HashSet<String>(Arrays.asList("a","b")));
    Mockito.verify(service).perform(Matchers.eq(new CollectionVerifier<String>(Arrays.asList("a","b"))));
}

public class CollectionVerifier<E> extends ArrayList<E> {

    public CollectionVerifier() {

    }

    public CollectionVerifier(final Collection<? extends E> c) {
        super(c);
    }

    @Override
    public boolean equals(final Object o) {
        if (o instanceof Collection<?>) {
            Collection<?> other = (Collection<?>) o;
                return this.size() == other.size() && this.containsAll(other);
        }
        return false;
    }

}
2jcobegt

2jcobegt6#

你可以直接写

verify(service).perform((Collection<String>) Matchers.argThat(contains("a", "b")));

从编译器的Angular 来看,这是将Iterable<String>转换为Collection<String>,这很好,因为后者是前者的子类型。在运行时,argThat将返回null,这样就可以传递给perform,而不需要ClassCastException。的内部结构进行验证,这是argThat所做的。

tjrkku2a

tjrkku2a7#

作为一种替代方法,可以将该方法更改为ArgumentCaptor

@SuppressWarnings("unchecked") // needed because of `List<String>.class` is not a thing
// suppression can be worked around by using @Captor on a field
ArgumentCaptor<List<String>> captor = ArgumentCaptor.forClass(List.class);

verify(service).perform(captor.capture());
assertThat(captor.getValue(), contains("a", "b"));

请注意,作为一个副作用,它将验证从Hamcrest库中分离出来,并允许您使用任何其他库(例如Truth):

assertThat(captor.getValue()).containsExactly("a", "b");

相关问题