I matcher mockito sono metodi statici e chiamate a tali metodi, che sostituiscono gli argomenti durante le chiamate a when
e verify
.
I matcher di Hamcrest (versione archiviata) (o matcher in stile Hamcrest) sono istanze di oggetti generiche e senza stato che implementano Matcher<T>
ed espongono un metodo matches(T)
che restituisce true se l'oggetto corrisponde ai criteri del Matcher. Sono intesi per essere privi di effetti collaterali e sono generalmente utilizzati in affermazioni come quella di seguito.
/* Mockito */ verify(foo).setPowerLevel(gt(9000));
/* Hamcrest */ assertThat(foo.getPowerLevel(), is(greaterThan(9000)));
Esistono matcher Mockito, separati dai matcher in stile Hamcrest, in modo che le descrizioni delle espressioni corrispondenti si adattino direttamente alle invocazioni dei metodi : i matcher Mockito restituiscono T
dove i metodi Matcher Hamcrest restituiscono oggetti Matcher (di tipo Matcher<T>
).
Matchers Mockito sono invocati attraverso metodi statici come eq
, any
, gt
e startsWith
su org.mockito.Matchers
e org.mockito.AdditionalMatchers
. Ci sono anche adattatori, che sono cambiati nelle versioni di Mockito:
- Per Mockito 1.x,
Matchers
alcune chiamate (come intThat
o argThat
) sono matcher Mockito che accettano direttamente i matcher Hamcrest come parametri. ArgumentMatcher<T>
esteso org.hamcrest.Matcher<T>
, che è stato utilizzato nella rappresentazione interna di Hamcrest ed era una classe base del matcher Hamcrest invece di qualsiasi tipo di matcher Mockito.
- Per Mockito 2.0+, Mockito non ha più una dipendenza diretta da Hamcrest.
Matchers
chiama phrased as intThat
o argThat
wrap ArgumentMatcher<T>
oggetti che non vengono più implementati org.hamcrest.Matcher<T>
ma utilizzati in modi simili. Gli adattatori Hamcrest come argThat
e intThat
sono ancora disponibili, ma sono stati MockitoHamcrest
invece spostati .
Indipendentemente dal fatto che gli abbinatori siano Hamcrest o semplicemente in stile Hamcrest, possono essere adattati in questo modo:
/* Mockito matcher intThat adapting Hamcrest-style matcher is(greaterThan(...)) */
verify(foo).setPowerLevel(intThat(is(greaterThan(9000))));
Nella dichiarazione precedente: foo.setPowerLevel
è un metodo che accetta un file int
. is(greaterThan(9000))
restituisce a Matcher<Integer>
, che non funzionerebbe come setPowerLevel
argomento. Il Mockito Matcher intThat
avvolge quel Matcher in stile Hamcrest e restituisce un in int
modo che possa apparire come argomento; I matcher mockito come gt(9000)
avvolgono l'intera espressione in una singola chiamata, come nella prima riga del codice di esempio.
Cosa fanno / restituiscono gli abbinatori
when(foo.quux(3, 5)).thenReturn(true);
Quando non si utilizzano corrispondenze di argomenti, Mockito registra i valori degli argomenti e li confronta con i loro equals
metodi.
when(foo.quux(eq(3), eq(5))).thenReturn(true); // same as above
when(foo.quux(anyInt(), gt(5))).thenReturn(true); // this one's different
Quando chiami un matcher come any
o gt
(maggiore di), Mockito memorizza un oggetto matcher che fa sì che Mockito salti quel controllo di uguaglianza e applichi la tua corrispondenza scelta. Nel caso argumentCaptor.capture()
memorizza un matcher che salva il suo argomento invece per un'ispezione successiva.
I matcher restituiscono valori fittizi come zero, raccolte vuote o null
. Mockito cerca di restituire un valore fittizio sicuro e appropriato, come 0 per anyInt()
o any(Integer.class)
o vuoto List<String>
per anyListOf(String.class)
. A causa della cancellazione del tipo, tuttavia, a Mockito mancano le informazioni sul tipo per restituire qualsiasi valore tranne che null
per any()
o argThat(...)
, che può causare un'eccezione NullPointerException se si tenta di "unbox automatico" un null
valore primitivo.
Ai matcher piacciono eq
e gt
accettano i valori dei parametri; idealmente, questi valori dovrebbero essere calcolati prima dell'inizio dello stubbing / verifica. Chiamare una finta mentre si prende in giro un'altra chiamata può interferire con lo stubbing.
I metodi Matcher non possono essere utilizzati come valori di ritorno; non c'è modo di formulare thenReturn(anyInt())
o thenReturn(any(Foo.class))
in Mockito, per esempio. Mockito deve sapere esattamente quale istanza restituire nelle chiamate stubbing e non sceglierà un valore di ritorno arbitrario per te.
Dettagli di implementazione
I matcher vengono memorizzati (come abbinamenti di oggetti in stile Hamcrest) in uno stack contenuto in una classe chiamata ArgumentMatcherStorage . MockitoCore e Matchers possiedono ciascuno un'istanza ThreadSafeMockingProgress , che contiene staticamente un ThreadLocal che contiene istanze MockingProgress. È questo MockingProgressImpl che contiene un ArgumentMatcherStorageImpl concreto . Di conseguenza, lo stato mock e matcher è statico ma con ambito thread in modo coerente tra le classi Mockito e Matchers.
La maggior parte delle chiamate matcher solo aggiungere questo stack, con un'eccezione per matchers come and
, or
enot
. Ciò corrisponde perfettamente (e si basa su) l' ordine di valutazione di Java , che valuta gli argomenti da sinistra a destra prima di invocare un metodo:
when(foo.quux(anyInt(), and(gt(10), lt(20)))).thenReturn(true);
[6] [5] [1] [4] [2] [3]
Questo sarà:
- Aggiungi
anyInt()
alla pila.
- Aggiungi
gt(10)
alla pila.
- Aggiungi
lt(20)
alla pila.
- Rimuovere
gt(10)
e lt(20)
ed aggiungere and(gt(10), lt(20))
.
- Call
foo.quux(0, 0)
, che (se non diversamente bloccato) restituisce il valore predefinito false
. Internamente Mockito segna quux(int, int)
come la chiamata più recente.
- Call
when(false)
, che scarta il suo argomento e si prepara allo stub del metodo quux(int, int)
identificato in 5. Gli unici due stati validi sono con lunghezza dello stack 0 (uguaglianza) o 2 (matchers), e ci sono due matcher nello stack (passaggi 1 e 4), quindi Mockito stub il metodo con un any()
matcher per il suo primo argomento e and(gt(10), lt(20))
per il secondo argomento e cancella lo stack.
Questo dimostra alcune regole:
Mockito non riesce a distinguere tra quux(anyInt(), 0)
e quux(0, anyInt())
. Entrambi sembrano una chiamata a quux(0, 0)
con un int matcher in pila. Di conseguenza, se usi un matcher, devi abbinare tutti gli argomenti.
L'ordine delle chiamate non è solo importante, è ciò che fa funzionare tutto questo . L'estrazione di abbinamenti alle variabili in genere non funziona, perché di solito cambia l'ordine di chiamata. L'estrazione di abbinamenti ai metodi, tuttavia, funziona alla grande.
int between10And20 = and(gt(10), lt(20));
/* BAD */ when(foo.quux(anyInt(), between10And20)).thenReturn(true);
// Mockito sees the stack as the opposite: and(gt(10), lt(20)), anyInt().
public static int anyIntBetween10And20() { return and(gt(10), lt(20)); }
/* OK */ when(foo.quux(anyInt(), anyIntBetween10And20())).thenReturn(true);
// The helper method calls the matcher methods in the right order.
Lo stack cambia abbastanza spesso che Mockito non può controllarlo con molta attenzione. Può controllare lo stack solo quando interagisci con Mockito o con un mock, e deve accettare matcher senza sapere se vengono usati immediatamente o abbandonati accidentalmente. In teoria, lo stack dovrebbe essere sempre vuoto al di fuori di una chiamata a when
o verify
, ma Mockito non può controllarlo automaticamente. Puoi controllare manualmente con Mockito.validateMockitoUsage()
.
In una chiamata a when
, Mockito chiama effettivamente il metodo in questione, che genererà un'eccezione se hai bloccato il metodo per generare un'eccezione (o richiedi valori diversi da zero o non nulli).
doReturn
e doAnswer
(ecc.) non invocano il metodo effettivo e sono spesso un'utile alternativa.
Se avessi chiamato un metodo fittizio nel mezzo dello stubbing (ad esempio per calcolare una risposta per un eq
matcher), Mockito avrebbe invece verificato la lunghezza dello stack rispetto a quella chiamata e probabilmente avrebbe fallito.
Se provi a fare qualcosa di sbagliato, come lo stubbing / la verifica di un metodo finale , Mockito chiamerà il metodo reale e lascerà anche gli abbinatori extra in pila . La final
chiamata al metodo potrebbe non generare un'eccezione, ma potresti ottenere un'eccezione InvalidUseOfMatchersException dai matcher vaganti quando interagisci con una simulazione.
Problemi comuni
InvalidUseOfMatchersException :
Verifica che ogni singolo argomento abbia esattamente una chiamata matcher, se usi tutti i matcher, e che non hai usato un matcher al di fuori di una chiamata when
or verify
. I matcher non dovrebbero mai essere usati come valori di ritorno stubbed o campi / variabili.
Verifica di non chiamare una finta come parte del fornire un argomento di corrispondenza.
Verifica di non provare a bloccare / verificare un metodo finale con un matcher. È un ottimo modo per lasciare un matcher in pila e, a meno che il tuo metodo finale non generi un'eccezione, questa potrebbe essere l'unica volta in cui ti rendi conto che il metodo che stai prendendo in giro è definitivo.
NullPointerException con argomenti primitivi: (Integer) any()
restituisce null mentre any(Integer.class)
restituisce 0; questo può causare un NullPointerException
se ti aspetti un int
invece di un numero intero. In ogni caso, preferisci anyInt()
, che restituirà zero e salterà anche il passaggio dell'auto-boxing.
NullPointerException o altre eccezioni: le chiamate a when(foo.bar(any())).thenReturn(baz)
verranno effettivamente chiamate foo.bar(null)
, cosa che potresti aver bloccato per generare un'eccezione quando ricevi un argomento null. Il passaggio a doReturn(baz).when(foo).bar(any())
salta il comportamento stub .
Risoluzione dei problemi generali
Usa MockitoJUnitRunner o chiama esplicitamente il validateMockitoUsage
tuo metodo tearDown
o @After
(cosa che il runner farebbe automaticamente per te). Questo ti aiuterà a determinare se hai abusato dei matcher.
Ai fini del debug, aggiungi direttamente le chiamate a validateMockitoUsage
nel codice. Questo lancerà se hai qualcosa in pila, che è un buon avvertimento di un cattivo sintomo.