java - Mockito y Hamcrest: ¿cómo verificar la invocación del argumento Collection?
generics (5)
Puede tener su propia implementación java.util.Collection y anular el método equals como se muestra a continuación.
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;
}
}
Me encuentro con un problema de genéricos con Mockito y Hamcrest.
Por favor asuma la siguiente interfaz:
public interface Service {
void perform(Collection<String> elements);
}
Y el siguiente fragmento de prueba:
Service service = mock(Service.class);
// ... perform business logic
verify(service).perform(Matchers.argThat(contains("a", "b")));
Por lo tanto, quiero verificar que mi lógica comercial realmente llamó al servicio con una colección que contiene "a" y "b" en ese orden.
Sin embargo, el tipo de devolución de contains(...)
es Matcher<Iterable<? extends E>>
Matcher<Iterable<? extends E>>
, por lo que Matchers.argThat(...)
devuelve Iterable<String>
en mi caso, que naturalmente no se aplica a la Collection<String>
requerida.
Sé que podría usar un captor de argumento como se propone en Hamcrest. HasItem y Mockito verifican la inconsistencia , pero me gustaría mucho no hacerlo.
¡Alguna sugerencia! ¡Gracias!
¿Por qué no simplemente verificar con los argumentos esperados, suponiendo que la lista solo contenga los dos elementos, por ejemplo:
final List<String> expected = Lists.newArrayList("a", "b");
verify(service).perform(expected);
Aunque estoy de acuerdo con Eugen en principio, creo que confiar en iguales para la comparación de cadenas es aceptable ... además, el contenedor contains
igual para la comparación de todos modos.
Puedes escribir
verify(service).perform((Collection<String>) Matchers.argThat(contains("a", "b")));
Desde el punto de vista del compilador, esto está Iterable<String>
un Iterable<String>
en una Collection<String>
que está bien, porque este último es un subtipo del anterior. En tiempo de ejecución, argThat
devolverá null
, por lo que se puede pasar para perform
sin una ClassCastException
. El punto importante al respecto es que el emparejador entra en la estructura interna de argumentos de Mockito para la verificación, que es lo que hace argThat
.
Si se queda atascado en situaciones como estas, recuerde que puede escribir un adaptador reutilizable muy pequeño.
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);
}
};
}
Tenga en cuenta que la solución de David anterior, con casting, es la respuesta correcta más corta.
Como alternativa, uno podría cambiar el enfoque de 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"));