¿Hay alguna manera de capturar una lista de tipos específicos usando mockitos ArgumentCaptore? Esto no funciona:
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(ArrayList.class);
¿Hay alguna manera de capturar una lista de tipos específicos usando mockitos ArgumentCaptore? Esto no funciona:
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(ArrayList.class);
Respuestas:
El problema genérico anidado se puede evitar con la anotación @Captor :
public class Test{
@Mock
private Service service;
@Captor
private ArgumentCaptor<ArrayList<SomeType>> captor;
@Before
public void init(){
MockitoAnnotations.initMocks(this);
}
@Test
public void shouldDoStuffWithListValues() {
//...
verify(service).doStuff(captor.capture()));
}
}
MockitoAnnotations.initMocks(this)
el @Before
método en lugar de usar un corredor que excluye la posibilidad de usar otro corredor. Sin embargo, +1, gracias por señalar la anotación.
Sí, este es un problema genérico general, no específico de simulacro.
No hay ningún objeto de clase ArrayList<SomeType>
y, por lo tanto, no puede escribir con seguridad dicho objeto a un método que requiera un Class<ArrayList<SomeType>>
.
Puede lanzar el objeto al tipo correcto:
Class<ArrayList<SomeType>> listClass =
(Class<ArrayList<SomeType>>)(Class)ArrayList.class;
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(listClass);
Esto le dará algunas advertencias sobre los lanzamientos inseguros y, por supuesto, su ArgumentCaptor no puede realmente diferenciar entre ArrayList<SomeType>
y ArrayList<AnotherType>
sin quizás inspeccionar los elementos.
(Como se mencionó en la otra respuesta, si bien este es un problema genérico general, existe una solución específica de Mockito para el problema de seguridad de tipo con la @Captor
anotación. Todavía no se puede distinguir entre an ArrayList<SomeType>
y an ArrayList<OtherType>
.)
Mire también el comentario de tenshi . Puede cambiar el código original de Paŭlo Ebermann a esto (mucho más simple)
final ArgumentCaptor<List<SomeType>> listCaptor
= ArgumentCaptor.forClass((Class) List.class);
ArgumentCaptor<List<SimeType>> argument = ArgumentCaptor.forClass((Class) List.class);
@SuppressWarnings("unchecked")
anotación sobre la línea de definición del captor de argumentos. Además, enviar a Class
es redundante.
Class
no es redundante en mis pruebas.
Si no le temes a la antigua semántica de estilo java (genérico sin tipo seguro), esto también funciona y es razonablemente simple:
ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
verify(subject.method(argument.capture()); // run your code
List<SomeType> list = argument.getValue(); // first captured List, etc.
List<String> mockedList = mock(List.class);
List<String> l = new ArrayList();
l.add("someElement");
mockedList.addAll(l);
ArgumentCaptor<List> argumentCaptor = ArgumentCaptor.forClass(List.class);
verify(mockedList).addAll(argumentCaptor.capture());
List<String> capturedArgument = argumentCaptor.<List<String>>getValue();
assertThat(capturedArgument, hasItem("someElement"));
Basado en los comentarios de @ tenshi y @ pkalinow (también felicitaciones a @rogerdpack), la siguiente es una solución simple para crear un captor de argumentos de lista que también deshabilita la advertencia "utiliza operaciones no verificadas o inseguras" :
@SuppressWarnings("unchecked")
final ArgumentCaptor<List<SomeType>> someTypeListArgumentCaptor =
ArgumentCaptor.forClass(List.class);
Ejemplo completo aquí y la compilación y prueba de CI correspondientes que se ejecutan aquí .
Nuestro equipo ha estado usando esto durante algún tiempo en nuestras pruebas unitarias y esta parece ser la solución más sencilla para nosotros.
Para una versión anterior de junit, puedes hacer
Class<Map<String, String>> mapClass = (Class) Map.class;
ArgumentCaptor<Map<String, String>> mapCaptor = ArgumentCaptor.forClass(mapClass);
Tuve el mismo problema con la actividad de prueba en mi aplicación de Android. Solía ActivityInstrumentationTestCase2
y MockitoAnnotations.initMocks(this);
no funcionaba. Resolví este problema con otra clase con campo respectivamente. Por ejemplo:
class CaptorHolder {
@Captor
ArgumentCaptor<Callback<AuthResponse>> captor;
public CaptorHolder() {
MockitoAnnotations.initMocks(this);
}
}
Luego, en el método de prueba de actividad:
HubstaffService hubstaffService = mock(HubstaffService.class);
fragment.setHubstaffService(hubstaffService);
CaptorHolder captorHolder = new CaptorHolder();
ArgumentCaptor<Callback<AuthResponse>> captor = captorHolder.captor;
onView(withId(R.id.signInBtn))
.perform(click());
verify(hubstaffService).authorize(anyString(), anyString(), captor.capture());
Callback<AuthResponse> callback = captor.getValue();
Hay un problema abierto en GitHub de Mockito sobre este problema exacto.
He encontrado una solución simple que no te obliga a usar anotaciones en tus pruebas:
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.MockitoAnnotations;
public final class MockitoCaptorExtensions {
public static <T> ArgumentCaptor<T> captorFor(final CaptorTypeReference<T> argumentTypeReference) {
return new CaptorContainer<T>().captor;
}
public static <T> ArgumentCaptor<T> captorFor(final Class<T> argumentClass) {
return ArgumentCaptor.forClass(argumentClass);
}
public interface CaptorTypeReference<T> {
static <T> CaptorTypeReference<T> genericType() {
return new CaptorTypeReference<T>() {
};
}
default T nullOfGenericType() {
return null;
}
}
private static final class CaptorContainer<T> {
@Captor
private ArgumentCaptor<T> captor;
private CaptorContainer() {
MockitoAnnotations.initMocks(this);
}
}
}
Lo que sucede aquí es que creamos una nueva clase con la @Captor
anotación e inyectamos el captor en ella. Luego simplemente extraemos el captor y lo devolvemos de nuestro método estático.
En su prueba, puede usarlo así:
ArgumentCaptor<Supplier<Set<List<Object>>>> fancyCaptor = captorFor(genericType());
O con una sintaxis que se parece a la de Jackson TypeReference
:
ArgumentCaptor<Supplier<Set<List<Object>>>> fancyCaptor = captorFor(
new CaptorTypeReference<Supplier<Set<List<Object>>>>() {
}
);
Funciona porque Mockito no necesita ningún tipo de información (a diferencia de los serializadores, por ejemplo).
ArrayList
). Siempre puedes usar laList
interfaz, y si quieres representar el hecho de que es covariante, entonces puedes usarextends
:ArgumentCaptor<? extends List<SomeType>>