Esiste un modo per acquisire un elenco di tipo specifico utilizzando mockitos ArgumentCaptore. Questo non funziona:
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(ArrayList.class);
Esiste un modo per acquisire un elenco di tipo specifico utilizzando mockitos ArgumentCaptore. Questo non funziona:
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(ArrayList.class);
Risposte:
Il problema generics nidificato può essere evitato con l' annotazione @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)
il @Before
metodo piuttosto che usare un corridore che esclude la possibilità di usare un altro corridore. Tuttavia, +1, grazie per aver sottolineato l'annotazione.
Sì, questo è un problema di generica generale, non specifico per il mockito.
Non esiste un oggetto di classe per ArrayList<SomeType>
, e quindi non è possibile digitare in modo sicuro un tale oggetto a un metodo che richiede unClass<ArrayList<SomeType>>
.
Puoi lanciare l'oggetto nel giusto tipo:
Class<ArrayList<SomeType>> listClass =
(Class<ArrayList<SomeType>>)(Class)ArrayList.class;
ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(listClass);
Ciò fornirà alcuni avvertimenti su cast non sicuri e, naturalmente, ArgumentCaptor non può davvero distinguere tra ArrayList<SomeType>
eArrayList<AnotherType>
senza forse ispezionare gli elementi.
(Come menzionato nell'altra risposta, sebbene si tratti di un problema di generica generale, esiste una soluzione specifica di Mockito per il problema della sicurezza del tipo con l' @Captor
annotazione. Non è ancora possibile distinguere tra an ArrayList<SomeType>
e an ArrayList<OtherType>
.)
Dai un'occhiata anche al commento di Tenshi . Puoi modificare il codice originale da Paŭlo Ebermann a questo (molto più semplice)
final ArgumentCaptor<List<SomeType>> listCaptor
= ArgumentCaptor.forClass((Class) List.class);
ArgumentCaptor<List<SimeType>> argument = ArgumentCaptor.forClass((Class) List.class);
@SuppressWarnings("unchecked")
annotazione sopra la riga di definizione del captor dell'argomento. Inoltre, il casting su Class
è ridondante.
Class
non è ridondante nei miei test.
Se non hai paura della vecchia semantica in stile java (non di tipo generico sicuro), anche questo funziona ed è ragionevolmente semplice:
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"));
Sulla base dei commenti di @ tenshi e @ pkalinow (anche complimenti a @rogerdpack), la seguente è una soluzione semplice per la creazione di un captor dell'argomento elenco che disabilita anche l' avviso "utilizza operazioni non controllate o non sicure" :
@SuppressWarnings("unchecked")
final ArgumentCaptor<List<SomeType>> someTypeListArgumentCaptor =
ArgumentCaptor.forClass(List.class);
L'esempio completo qui e la corrispondente compilazione e test di CI di passaggio vengono eseguiti qui .
Il nostro team lo utilizza da qualche tempo nei nostri test unitari e questa sembra la soluzione più semplice per noi.
Per una versione precedente di junit, puoi farlo
Class<Map<String, String>> mapClass = (Class) Map.class;
ArgumentCaptor<Map<String, String>> mapCaptor = ArgumentCaptor.forClass(mapClass);
Ho avuto lo stesso problema con l'attività di test nella mia app Android. Ho usato ActivityInstrumentationTestCase2
e MockitoAnnotations.initMocks(this);
non ha funzionato. Ho risolto questo problema con un'altra classe rispettivamente con il campo. Per esempio:
class CaptorHolder {
@Captor
ArgumentCaptor<Callback<AuthResponse>> captor;
public CaptorHolder() {
MockitoAnnotations.initMocks(this);
}
}
Quindi, nel metodo di test dell'attività:
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();
C'è un problema aperto nel GitHub di Mockito su questo esatto problema.
Ho trovato una soluzione semplice che non ti costringe a utilizzare le annotazioni nei tuoi test:
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);
}
}
}
Quello che succede qui è che creiamo una nuova classe con l' @Captor
annotazione e iniettiamo il captor in essa. Quindi estraiamo semplicemente il captatore e lo restituiamo dal nostro metodo statico.
Nel tuo test puoi usarlo in questo modo:
ArgumentCaptor<Supplier<Set<List<Object>>>> fancyCaptor = captorFor(genericType());
O con una sintassi che ricorda quella di Jackson TypeReference
:
ArgumentCaptor<Supplier<Set<List<Object>>>> fancyCaptor = captorFor(
new CaptorTypeReference<Supplier<Set<List<Object>>>>() {
}
);
Funziona, perché Mockito in realtà non ha bisogno di informazioni sul tipo (diversamente dai serializzatori, per esempio).
ArrayList
). Puoi sempre usare l'List
interfaccia e, se vuoi rappresentare il fatto, che è covariante, puoi usareextends
:ArgumentCaptor<? extends List<SomeType>>