我遇到了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中提出的参数捕获者,但我非常愿意这样做。
任何建议! 谢谢!
答案 0 :(得分:28)
你可以写
verify(service).perform((Collection<String>) Matchers.argThat(contains("a", "b")));
从编译器的角度来看,这是将Iterable<String>
转换为Collection<String>
,这很好,因为后者是前者的子类型。在运行时,argThat
将返回null
,因此可以在没有perform
的情况下传递给ClassCastException
。关于它的重要一点是,匹配器进入Mockito的内部验证参数结构,这是argThat
所做的。
答案 1 :(得分:7)
如果您遇到类似这样的情况,请记住您可以编写一个非常小的可重复使用的适配器。
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);
}
};
}
请注意,上面David的解决方案是最简单的正确答案。
答案 2 :(得分:6)
作为替代方案,可以将方法改为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");
答案 3 :(得分:1)
为什么不用预期的参数进行验证,假设列表只包含两个项目,例如:
final List<String> expected = Lists.newArrayList("a", "b");
verify(service).perform(expected);
虽然我原则上同意Eugen,但我认为依靠equals进行字符串比较是可以接受的......此外,contains
匹配器使用equals进行比较。
答案 4 :(得分:0)
您可以拥有自己的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;
}
}
答案 5 :(得分:0)
您可以将自己的λ作为ArgumentMatcher
when(myClass.myMethod(argThat(arg -> arg.containsAll(asList(1,2))))
.thenReturn(...);