Java Mockito 和 Hamcrest:如何验证 Collection 参数的调用?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/20441594/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Mockito and Hamcrest: how to verify invocation of Collection argument?
提问by Philipp Jardas
I'm running into a generics problem with Mockito and Hamcrest.
我遇到了 Mockito 和 Hamcrest 的泛型问题。
Please assume the following interface:
请假设如下界面:
public interface Service {
void perform(Collection<String> elements);
}
And the following test snippet:
以及以下测试片段:
Service service = mock(Service.class);
// ... perform business logic
verify(service).perform(Matchers.argThat(contains("a", "b")));
So I want to verify that my business logic actually called the service with a collection that contains "a" and "b" in that order.
因此,我想验证我的业务逻辑是否实际调用了具有按该顺序包含“a”和“b”的集合的服务。
However, the return type of contains(...)
is Matcher<Iterable<? extends E>>
, so Matchers.argThat(...)
returns Iterable<String>
in my case, which naturally does not apply to the required Collection<String>
.
但是, 的返回类型contains(...)
是Matcher<Iterable<? extends E>>
,因此在我的情况下Matchers.argThat(...)
返回Iterable<String>
,这自然不适用于所需的Collection<String>
.
I know that I could use an argument captor as proposed in Hamcrest hasItem and Mockito verify inconsistency, but I would very much like not to.
我知道我可以使用Hamcrest hasItem 和 Mockito verify inconsistency 中提出的参数捕获器,但我非常不想这样做。
Any suggestions! Thanks!
有什么建议!谢谢!
采纳答案by Dawood ibn Kareem
You can just write
你可以写
verify(service).perform((Collection<String>) Matchers.argThat(contains("a", "b")));
From the compiler's point of view, this is casting an Iterable<String>
to a Collection<String>
which is fine, because the latter is a subtype of the former. At run time, argThat
will return null
, so that can be passed to perform
without a ClassCastException
. The important point about it is that the matcher gets onto Mockito's internal structure of arguments for verification, which is what argThat
does.
从编译器的角度来看,这是将 an 强制转换Iterable<String>
为 aCollection<String>
很好,因为后者是前者的子类型。在运行时,argThat
将返回null
,因此可以在perform
没有ClassCastException
. 关于它的重要一点是匹配器进入 Mockito 的内部参数结构进行验证,这就是argThat
它的作用。
回答by Dev Blanked
You could have your own java.util.Collection implementation and override the equals method like below.
您可以拥有自己的 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;
}
}
回答by Jeff Bowman
If you get stuck in situations like these, remember that you can write a very small reusable adapter.
如果您遇到此类情况,请记住您可以编写一个非常小的可重用适配器。
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);
}
};
}
Note that David's solution above, with casting, is the shortest right answer.
请注意,上面 David 的解决方案(带有强制转换)是最短的正确答案。
回答by Jonathan
Why not just verify with the expected arguments, assuming the list only contains the two items, e.g.:
为什么不使用预期的参数进行验证,假设列表只包含两个项目,例如:
final List<String> expected = Lists.newArrayList("a", "b");
verify(service).perform(expected);
Whilst I agree with Eugen in principle, I think that relying on equals for String comparison is acceptable... besides, the contains
matcher uses equals for comparison anyway.
虽然我原则上同意 Eugen,但我认为依靠 equals 进行字符串比较是可以接受的......此外,contains
匹配器无论如何都使用 equals 进行比较。
回答by TWiStErRob
As an alternative one could change the approach to ArgumentCaptor
:
作为替代方案,可以将方法更改为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"));
Notice, that as a side effect this decouples the verification from the Hamcrest library, and allows you to use any other library (e.g. Truth):
请注意,作为副作用,这将验证与 Hamcrest 库分离,并允许您使用任何其他库(例如 Truth):
assertThat(captor.getValue()).containsExactly("a", "b");
回答by dehasi
You can put your own lambda as an ArgumentMatcher
您可以将自己的 lambda 作为 ArgumentMatcher
when(myClass.myMethod(argThat(arg -> arg.containsAll(asList(1,2))))
.thenReturn(...);