Come acquisire un elenco di tipo specifico con mockito


301

Esiste un modo per acquisire un elenco di tipo specifico utilizzando mockitos ArgumentCaptore. Questo non funziona:

ArgumentCaptor<ArrayList<SomeType>> argument = ArgumentCaptor.forClass(ArrayList.class);

8
Trovo che sia una pessima idea usare l'implementazione di elenchi concreti qui ( ArrayList). Puoi sempre usare l' Listinterfaccia e, se vuoi rappresentare il fatto, che è covariante, puoi usare extends:ArgumentCaptor<? extends List<SomeType>>
tenshi

Risposte:


533

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()));
    }
}

70
Preferisco usare MockitoAnnotations.initMocks(this)il @Beforemetodo piuttosto che usare un corridore che esclude la possibilità di usare un altro corridore. Tuttavia, +1, grazie per aver sottolineato l'annotazione.
Giovanni B,

4
Non sono sicuro che questo esempio sia completo. Ottengo ... Errore: (240, 40) java: il captor variabile potrebbe non essere stato inizializzato, mi piace la risposta di tenshi sotto
Michael Dausmann,

1
Ho riscontrato lo stesso problema e ho trovato questo post sul blog che mi ha aiutato un po ': blog.jdriven.com/2012/10/… . Include un passaggio per usare MockitoAnnotations.initMocks dopo aver inserito l'annotazione nella tua classe. Una cosa che ho notato è che non puoi averlo all'interno di una variabile locale.
SlopeOak,

1
@ chamzz.dot ArgumentCaptor <ArrayList <SomeType>> captor; sta già acquisendo un array di "SomeType" <- che è un tipo specifico, no?
Miguel R. Santaella,

1
Di solito preferisco List invece di ArrayList nella dichiarazione Captor: ArgumentCaptor <List <SomeType>> captor;
Miguel R. Santaella,

146

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' @Captorannotazione. Non è ancora possibile distinguere tra an ArrayList<SomeType>e an ArrayList<OtherType>.)

Modificare:

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);

49
L'esempio che hai mostrato può essere semplificato, in base al fatto che Java fa l'inferenza del tipo per le chiamate al metodo statico:ArgumentCaptor<List<SimeType>> argument = ArgumentCaptor.forClass((Class) List.class);
tenshi

4
Per disabilitare l' avviso per le operazioni non controllate o non sicure , utilizzare l' @SuppressWarnings("unchecked")annotazione sopra la riga di definizione del captor dell'argomento. Inoltre, il casting su Classè ridondante.
sig.

1
Il casting a Classnon è ridondante nei miei test.
Wim Deblauwe,

16

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.

2
È possibile aggiungere @SuppressWarnings ("rawtypes") prima della dichiarazione per disabilitare gli avvisi.
pkalinow,

9
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"));

4

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.


2

Per una versione precedente di junit, puoi farlo

Class<Map<String, String>> mapClass = (Class) Map.class;
ArgumentCaptor<Map<String, String>> mapCaptor = ArgumentCaptor.forClass(mapClass);

1

Ho avuto lo stesso problema con l'attività di test nella mia app Android. Ho usato ActivityInstrumentationTestCase2e 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();

0

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' @Captorannotazione 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).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.