簡體   English   中英

Mockito 和 Hamcrest:如何驗證 Collection 參數的調用?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM