Подтвердить что ты не робот

Mockito и Hamcrest: как проверить вызов аргумента Collection?

Я сталкиваюсь с проблемой дженериков с 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 и Mockito, чтобы проверить несогласованность, но я бы очень хотел этого не делать.

Любые предложения! Спасибо!

4b9b3361

Ответ 1

Вы можете просто написать

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

С точки зрения компилятора это отличает Iterable<String> до a Collection<String>, потому что последнее является подтипом первого. Во время выполнения argThat вернет null, поэтому его можно передать в perform без ClassCastException. Важным моментом в этом является то, что совпадёт на внутреннюю структуру аргументов Mockito для проверки, что и делает argThat.

Ответ 2

Если вы застряли в подобных ситуациях, помните, что вы можете написать очень маленький многоразовый адаптер.

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 выше, с литьем, является самым коротким правильным ответом.

Ответ 3

В качестве альтернативы можно было бы изменить подход к 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"));

Ответ 4

Почему бы просто не проверить с ожидаемыми аргументами, считая, что список содержит только два элемента, например:

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

Хотя я согласен с Eugen в принципе, я думаю, что полагаться на equals для сравнения String допустимо... кроме того, contains -сервер использует equals для сравнения в любом случае.

Ответ 5

У вас может быть ваша собственная реализация 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;
    }

}

Ответ 6

Вы можете поместить свою собственную лямбду в качестве ArgumentMatcher

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