[英]Mockito and Hamcrest: how to verify invocation of Collection argument?
我遇到了 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>>
Matcher<Iterable<? extends E>>
,所以Matchers.argThat(...)
在我的情況下返回Iterable<String>
,這自然不適用於所需的Collection<String>
。
我知道我可以使用Hamcrest hasItem 和 Mockito verify inconsistency中提出的參數捕獲器,但我非常不想這樣做。
有什么建議么! 謝謝!
你可以寫
verify(service).perform((Collection<String>) Matchers.argThat(contains("a", "b")));
從編譯器的角度來看,這是將Iterable<String>
強制轉換為Collection<String>
,這很好,因為后者是前者的子類型。 在運行時, argThat
將返回null
,因此可以在沒有ClassCastException
的情況下將其傳遞給perform
。 關於它的重要一點是,匹配器進入 Mockito 的內部參數結構以進行驗證,這就是argThat
所做的。
作為替代方案,可以更改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");
如果您遇到此類情況,請記住您可以編寫一個非常小的可重用適配器。
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);
}
};
}
請注意,上面大衛的解決方案,帶有強制轉換,是最短的正確答案。
您可以將自己的 lambda 作為ArgumentMatcher
when(myClass.myMethod(argThat(arg -> arg.containsAll(asList(1,2))))
.thenReturn(...);
假設列表僅包含兩項,為什么不直接驗證預期的參數,例如:
final List<String> expected = Lists.newArrayList("a", "b");
verify(service).perform(expected);
雖然我原則上同意 Eugen,但我認為依靠 equals 進行字符串比較是可以接受的......此外, contains
匹配器無論如何都使用 equals 進行比較。
與此處的另一個答案類似,您可以執行以下操作:
verify(yourmock, times(1)).yourmethod(argThat(arg -> arg.containsAll(asList("a", "b"))));
您可以擁有自己的 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;
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.