mockito Tutorial
Inizia con mockito
Ricerca…
Osservazioni
Mockito è un framework java Mocking che mira a fornire la capacità di scrivere un test unitario leggibile utilizzando la sua semplice API. Si differenzia dagli altri framework di derisione lasciando il modello di verifica di tipo expect-run che viene utilizzato dalla maggior parte degli altri framework.
Al contrario, conosce solo un modo per simulare classi e interfacce (non finali) e consente di verificare e stub in base a parametri di confronto flessibili.
L'attuale versione 1.10.19 è meglio ottenuta usando Maven
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>
</dependency>
o gradle
repositories { jcenter() }
dependencies { testCompile "org.mockito:mockito-core:1.+" }
La versione 2 è ancora in beta.
Versioni
Versione | Maven Central | Note di rilascio | Data di rilascio |
---|---|---|---|
2.1.0 | Mockito-core | i cambiamenti | 2016/10/04 |
1.10.19 | Mockito-core | i cambiamenti | 2014/12/31 |
Test unitario semplice con Mockito
La classe che testeremo è:
public class Service {
private Collaborator collaborator;
public Service(Collaborator collaborator) {
this.collaborator = collaborator;
}
public String performService(String input) {
return collaborator.transformString(input);
}
}
Il suo collaboratore è:
public class Collaborator {
public String transformString(String input) {
return doStuff();
}
private String doStuff() {
// This method may be full of bugs
. . .
return someString;
}
}
Nel nostro test, vogliamo rompere la dipendenza da Collaborator
e dai suoi bug, quindi prendiamo in giro Collaborator
:
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import org.junit.Test;
public class ServiceTest {
@Test
public void testPerformService() throws Exception {
// Configure mock
Collaborator collaboratorMock = mock(Collaborator.class);
doReturn("output").when(collaboratorMock).transformString("input");
// Perform the test
Service service = new Service(collaboratorMock);
String actual = service.performService("input");
// Junit asserts
String expected = "output";
assertEquals(expected, actual);
}
}
Utilizzo delle annotazioni Mockito
La classe che testeremo è:
public class Service{
private Collaborator collaborator;
public Service(Collaborator collaborator){
this.collaborator = collaborator;
}
public String performService(String input){
return collaborator.transformString(input);
}
}
Il suo collaboratore è:
public class Collaborator {
public String transformString(String input){
return doStuff();
}
private String doStuff()
{
// This method may be full of bugs
. . .
return someString;
}
}
Nel nostro test, vogliamo infrangere la dipendenza da Collaborator
e i suoi bug, quindi prendiamo in giro Collaborator
. L' @Mock
dell'annotazione @Mock
è un modo conveniente per creare diverse istanze di mock per ogni test:
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.InjectMocks;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class ServiceTest {
@Mock
private Collaborator collaboratorMock;
@InjectMocks
private Service service;
@Test
public void testPerformService() throws Exception {
// Configure mock
doReturn("output").when(collaboratorMock).transformString("input");
// Perform the test
String actual = service.performService("input");
// Junit asserts
String expected = "output";
assertEquals(expected, actual);
}
@Test(expected=Exception.class)
public void testPerformServiceShouldFail() throws Exception {
// Configure mock
doThrow(new Exception()).when(collaboratorMock).transformString("input");
// Perform the test
service.performService("input");
}
}
Mockito proverà a risolvere l'iniezione di dipendenza nel seguente ordine:
- Iniezione basata su un costruttore - i mock vengono iniettati nel costruttore con la maggior parte degli argomenti (se non è possibile trovare alcuni argomenti, vengono passati i valori null). Se un oggetto è stato creato correttamente tramite il costruttore, non verranno applicate altre strategie.
- Iniezione basata su setter : i mock sono iniettati per tipo. Se ci sono diverse proprietà dello stesso tipo, i nomi delle proprietà e i nomi simulati saranno abbinati.
- Iniezione diretta sul campo - come per l'iniezione basata sul setter.
Si noti che non viene segnalato alcun errore nel caso in cui una delle strategie sopra menzionate fallisse.
Consultare le ultime @InjectMocks
per informazioni più dettagliate su questo meccanismo nell'ultima versione di Mockito.
Installazione e configurazione
Installazione
Il modo migliore per installare Mockito è dichiarare una dipendenza da mockito-core
con un sistema di compilazione a scelta. A partire dal 22 luglio 2016, l'ultima versione non beta è 1.10.19, ma 2.x è già incoraggiata per la migrazione .
Maven
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
Gradle
repositories { jcenter() }
dependencies { testCompile "org.mockito:mockito-core:1.+" }
C'è anche il mockito-all
che contiene Hamcrest e Objenesis oltre a Mockito stesso. Viene distribuito tramite Maven principalmente per gli utenti di formiche, ma la distribuzione è stata interrotta in Mockito 2.x.
Importare
La maggior parte delle strutture di Mockito sono metodi statici di org.mockito.Mockito
. In questo modo, Mockito può essere importato staticamente in una classe in questo modo:
import static org.mockito.Mockito.*;
Il punto di ingresso della documentazione si trova nella javadoc di questa classe.
Mock alcuni metodi su un oggetto
Solo alcuni metodi di un oggetto possono essere derisi usando spy()
di mockito.
Ad esempio, immagina che la classe del metodo richieda il funzionamento di alcuni servizi Web.
public class UserManager {
List<User> users;
public UserManager() {
user = new LinkedLisk<User>();
}
public void addUser(User user) {
if (isValid(user)) {
user.add(user);
} else {
throw new NotValidUserException();
}
}
protected boolean isValid(User user) {
//some online web service to check if user is valid
}
public int numberOfUsers() {
return users.size();
}
}
addUser
metodo addUser
deve essere testato per fare un Test utile per UserManager
. Tuttavia, qui viene trovata una dipendenza, isValid
richiede un servizio web esterno che non è contenuto nel nostro codice. Quindi, questa dipendenza esterna dovrebbe essere neutralizzata.
In questo caso, se isValid
solo isValid
sarai in grado di testare il resto dei metodi UserManager
.
@Test
public void testAddUser() {
User user = mock(User.class);
UserManager manager = spy(new UserManager());
//it forces to manager.isValid to return true
doReturn(true).when(manager).isValid(anyObject());
manager.addUser(user);
assertTrue(manager.numberOfUsers(), 1);
}
È possibile verificare facilmente lo scenario in cui l' user
non è valido.
@Test(expectedExceptions = NotValidUserException.class)
public void testNotValidAddUser() {
User user = mock(User.class);
UserManager manager = spy(new UserManager());
//it forces to manager.isValid to return false
doReturn(false).when(manager).isValid(anyObject());
manager.addUser(user);
}
Semplice test Mockito minimale
Questo esempio mostra un test Mockito minimo usando un ArrayList
fittato:
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
import java.util.ArrayList;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class MockitoTest
{
@Mock
ArrayList<String> listMock;
@Test
public void testAppend() {
// configure the mock to return "foobar" whenever "get()"
// is called on "listMock" with an int value as parameter
doReturn("foobar").when(listMock).get(anyInt());
String result = listMock.get(0);
assertEquals("foobar", result);
}
}
Verifica degli argomenti con ArgumentCaptor
Per convalidare argomenti con metodi chiamati su un mock, utilizzare la classe ArgumentCaptor
. Questo ti permetterà di estrarre gli argomenti nel tuo metodo di test ed eseguire asserzioni su di essi.
Questo esempio verifica un metodo che aggiorna il nome di un utente con un dato ID. Il metodo carica l'utente, aggiorna l'attributo name
con il valore dato e lo salva in seguito. Il test vuole verificare che l'argomento passato al metodo di save
sia un oggetto User
con l'ID e il nome corretti.
// This is mocked in the test
interface UserDao {
void save(User user);
}
@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
@Mock
UserDao userDao;
@Test
public void testSetNameForUser() {
UserService serviceUnderTest = new UserService(userDao);
serviceUnderTest.setNameForUser(1L, "John");
ArgumentCaptor<User> userArgumentCaptor = ArgumentCaptor.forClass(User.class);
verify(userDao).save(userArgumentCaptor.capture());
User savedUser = userArgumentCaptor.getValue();
assertTrue(savedUser.getId() == 1);
assertTrue(savedUser.getName().equals("John"));
}
}
Verifica degli argomenti con ArgumentMatcher
Mockito fornisce un'interfaccia Matcher<T>
insieme ad una classe ArgumentMatcher<T>
astratta per verificare gli argomenti. Utilizza un approccio diverso allo stesso caso d'uso rispetto a ArgumentCaptor
. Inoltre, ArgumentMatcher può essere utilizzato anche in simulazione. Entrambi i casi d'uso fanno uso del metodo Mockito.argThat()
che fornisce un codice di test ragionevolmente leggibile.
verify(someMock).someMethod(Mockito.argThat(new ArgumentMatcher<String>() {
@Override
public boolean matches(Object o) {
return o instanceof String && !((String)o).isEmpty();
}
});
Da JavaDocs di ArgumentMatcher:
Avvertenza: essere ragionevoli nell'usare la complicata corrispondenza degli argomenti, in particolare i parametri di confronto personalizzati, in quanto può rendere il test meno leggibile. A volte è meglio implementare equals () per gli argomenti passati a mock (Mockito usa naturalmente equals () per la corrispondenza degli argomenti). Questo può rendere il test più pulito.
Crea oggetti derisi da Mockito
Ci sono due modi per creare oggetti derisi da Mockito:
- tramite annotazione
- via funzione di simulazione
Tramite annotazione:
Con un corridore di test JUnit:
@RunWith(MockitoJUnitRunner.class)
public class FooTest {
@Mock
private Bar barMock;
// ...
}
Puoi anche utilizzare JUnit @Rule
, che fornisce le stesse funzionalità di MockitoJUnitRunner
, ma non ha bisogno di un @RunWith
prova @RunWith
:
public class FooTest {
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
@Mock
private Bar barMock;
// ...
}
Se non puoi usare @RunWith
o l'annotazione @Rule
puoi anche @Rule
mock "per mano":
public class FooTest {
@Mock
private Bar barMock;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
// ...
}
Via funzione fittizia:
public class FooTest {
private Bar barMock = Mockito.mock(Bar.class);
// ...
}
A causa della cancellazione dei tipi, non è possibile prendere in giro una classe generica come sopra. Devi prendere in giro la classe base e trasmettere espressamente il giusto tipo generico:
public class FooTest {
private Bar<String> genericBarMock = (Bar<String>) Mockito.mock(Bar.class);
// ...
}
Aggiungi comportamento all'oggetto deriso
Mockito.when(mock.returnSomething()).thenReturn("my val");
mock.returnSomething(); // returns "my val"
mock.returnSomething(); // returns "my val" again
mock.returnSomething(); // returns "my val" again and again and again...
Se desideri un valore diverso in seconda chiamata, puoi aggiungere l'argomento di ritorno desiderato al metodo thenReturn:
Mockito.when(mock.returnSomething()).thenReturn("my val", "other val");
mock.returnSomething(); // returns "my val"
mock.returnSomething(); // returns "other val"
mock.returnSomething(); // returns "other val" again
Se chiamerai il metodo senza aggiungere comportamenti a simulare, restituirà null:
barMock.mock.returnSomethingElse(); // returns null
Nel caso in cui il metodo mocked abbia parametri, dovresti dichiarare anche i valori:
Mockito.when(mock.returnSomething("param 1")).thenReturn("my val 1");
Mockito.when(mock.returnSomething("param 2")).thenReturn("my val 2");
mock.returnSomething("param 1"); // returns "my val 1"
mock.returnSomething("param 2"); // returns "my val 2"
mock.returnSomething("param 3"); // returns null
Se non ti interessa il valore del parametro puoi utilizzare Matchers.any ():
Mockito.when(mock.returnSomething(Matchers.any())).thenReturn("p1");
mock.returnSomething("param 1"); // returns "p1"
mock.returnSomething("param other"); // returns "p1"
Per generare un'eccezione, usa il metodo Then:
Mockito.when(mock.returnSomething()).thenThrow(new Exception());
mock.returnSomething(); // throws Exception
Controlla gli argomenti passati a simulare
Supponiamo che abbiamo questa classe e vorremmo testare il metodo doSmth
. In questo caso vogliamo vedere se il parametro "val" è passato a foo
. Oggetto foo
è deriso.
public class Bar {
private final Foo foo;
public Bar(final Foo foo) {
this.foo = foo;
}
public void doSmth() {
foo.bla("val");
}
}
Possiamo raggiungere questo obiettivo con ArgumentCaptor
:
@Mock
private Foo fooMock;
@InjectMocks
private Bar underTest;
@Captor
private ArgumentCaptor<String> stringCaptor;
@Test
public void should_test_smth() {
underTest.doSmth();
Mockito.verify(fooMock).bla(stringCaptor.capture());
assertThat(stringCaptor.getValue(), is("val"));
}
Verifica le chiamate di metodo su oggetto deriso
Per verificare se un metodo è stato chiamato su un oggetto Mockito.verify
è possibile utilizzare il metodo Mockito.verify
:
Mockito.verify(someMock).bla();
In questo esempio, affermiamo che il metodo bla
stato chiamato someMock
mock someMock
.
Puoi anche verificare se è stato chiamato un metodo con determinati parametri:
Mockito.verify(someMock).bla("param 1");
Se si desidera verificare che non sia stato chiamato un metodo, è possibile passare un ulteriore parametro VerificationMode
per verify
:
Mockito.verify(someMock, Mockito.times(0)).bla();
Questo funziona anche se si desidera verificare che questo metodo sia stato chiamato più di una volta (in questo caso controlliamo che il metodo bla
stato chiamato 23 volte):
Mockito.verify(someMock, Mockito.times(23)).bla();
Questi sono più esempi per il parametro VerificationMode
, che fornisce un maggiore controllo sul numero di volte in cui un metodo dovrebbe essere chiamato:
Mockito.verify(someMock, Mockito.never()).bla(); // same as Mockito.times(0)
Mockito.verify(someMock, Mockito.atLeast(3)).bla(); // min 3 calls
Mockito.verify(someMock, Mockito.atLeastOnce()).bla(); // same as Mockito.atLeast(1)
Mockito.verify(someMock, Mockito.atMost(3)).bla(); // max 3 calls
Stubbing metodi void
void
metodi void
possono essere stubati usando la famiglia di metodi doThrow () , doAnswer () , doNothing () , doCallRealMethod () .
Runnable mock = mock(Runnable.class);
doThrow(new UnsupportedOperationException()).when(mock).run();
mock.run(); // throws the UnsupportedOperationException
Si noti che i metodi void
non possono essere stubati usando when(..)
perché il compilatore non ama i metodi void
come argomento.