mockitoAan de slag met mockito


Opmerkingen

Mockito is een Java Mocking-raamwerk dat de mogelijkheid biedt om leesbare unit-tests schoon te schrijven met behulp van de eenvoudige API. Het verschilt van andere spotkaders door het verwacht-run-verificatiepatroon te verlaten dat de meeste andere kaders gebruiken.

In plaats daarvan kent het slechts één manier om (niet-definitieve) klassen en interfaces te bespotten en maakt het mogelijk om te verifiëren en te stubben op basis van flexibele argumentmatchers.

De huidige versie 1.10.19 kan het beste worden verkregen met maven

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>1.10.19</version>
</dependency>

of gradle

repositories { jcenter() }
dependencies { testCompile "org.mockito:mockito-core:1.+" }

Versie 2 is nog in bèta.

versies

Versie Maven Central Release-opmerkingen Publicatiedatum
2.1.0 Mockito core veranderingen 2016/10/04
1.10.19 Mockito core veranderingen 2014/12/31

Gedrag toevoegen aan bespot object

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...
 

Als u bij de tweede aanroep een andere waarde wilt, kunt u het gewenste retourargument toevoegen aan de methode dan Return:

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
 

Als je de methode aanroept zonder gedrag aan mock toe te voegen, keert het null terug:

barMock.mock.returnSomethingElse(); // returns null
 

In het geval dat de bespotte methode parameters heeft, moet u ook waarden declareren:

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
 

Als je niet om param-waarde geeft, kun je Matchers.any () gebruiken:

Mockito.when(mock.returnSomething(Matchers.any())).thenReturn("p1");

mock.returnSomething("param 1"); // returns "p1"
mock.returnSomething("param other"); // returns "p1"
 

Gebruik de methode Throw om uitzonderingen te maken:

Mockito.when(mock.returnSomething()).thenThrow(new Exception());

mock.returnSomething(); // throws Exception
 

Controleer argumenten doorgegeven aan mock

Laten we aannemen dat we deze klasse hebben en dat we de doSmth methode willen testen. In dit geval willen we zien of parameter "val" wordt doorgegeven aan foo . Object foo wordt bespot.

public class Bar {

    private final Foo foo;

    public Bar(final Foo foo) {
        this.foo = foo;
    }

    public void doSmth() {
        foo.bla("val");
    }
}
 

We kunnen dit bereiken met 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"));
}
 

Maak objecten bespot door Mockito

Er zijn twee manieren om een object te maken dat door Mockito is bespot:

  • via annotatie
  • via mock-functie

Via annotatie:

Met een JUnit-testloper:

@RunWith(MockitoJUnitRunner.class)
public class FooTest {
    @Mock
    private Bar barMock;

    // ...
}
 

U kunt ook Mockito's JUnit @Rule , die dezelfde functionaliteit biedt als de MockitoJUnitRunner , maar geen @RunWith nodig heeft:

public class FooTest {
    @Rule
    public MockitoRule mockito = MockitoJUnit.rule();        

    @Mock
    private Bar barMock;

    // ...
}
 

Als je @RunWith of de @Rule @RunWith niet kunt gebruiken, kun je ook "per hand" @Rule :

public class FooTest {
    @Mock
    private Bar barMock;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }

    // ...
}
 

Via mock-functie:

public class FooTest {
    private Bar barMock = Mockito.mock(Bar.class);

    // ...
}
 

Vanwege het wissen van het type, kunt u niet spotten met een generieke klasse zoals hierboven. U moet de basisklasse bespotten en expliciet naar het juiste generieke type casten:

public class FooTest {
    private Bar<String> genericBarMock = (Bar<String>) Mockito.mock(Bar.class);

    // ...
}
 

Installatie en configuratie

Installatie

De beste manier om Mockito te installeren is om een afhankelijkheid van mockito-core met een build-systeem naar keuze. Vanaf 22 juli 2016 is de nieuwste niet-bètaversie 1.10.19, maar 2.x wordt al aangemoedigd om naar te migreren .

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.+" }
 

Er is ook mockito-all dat Hamcrest en Objenesis bevat naast Mockito zelf. Het wordt voornamelijk via Maven geleverd voor miergebruikers, maar de distributie is stopgezet in Mockito 2.x.


Importeren

De meeste Mockito-voorzieningen zijn statische methoden van org.mockito.Mockito . Zo kan Mockito op deze manier statisch in een klasse worden geïmporteerd:

import static org.mockito.Mockito.*;
 

Het toegangspunt voor documentatie bevindt zich in de javadoc van deze klasse.

Bespot enkele methoden op een object

Slechts enkele methoden van een object kunnen worden bespot met spy() van mockito.

Stel je bijvoorbeeld voor dat voor methodieklasse een webservice nodig is om te werken.

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 methode addUser moet worden getest om een nuttige test voor UserManager . Er is hier echter een afhankelijkheid, isValid vereist een externe webservice die niet in onze code staat. Vervolgens moet deze externe afhankelijkheid worden geneutraliseerd.

In dit geval, als u alleen isValid kunt u de rest van de UserManager methoden testen.

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

U kunt eenvoudig het scenario controleren waarin de user niet geldig is.

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

Eenvoudige minimale Mockito-test

Dit voorbeeld toont een minimale Mockito-test met een mocked ArrayList :

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

Eenvoudige eenheidstest met Mockito

De les die we gaan testen is:

public class Service {

    private Collaborator collaborator;

    public Service(Collaborator collaborator) {
        this.collaborator = collaborator;
    }
    
    public String performService(String input) {
        return collaborator.transformString(input);
    }
}
 

De medewerker is:

public class Collaborator {

    public String transformString(String input) {
        return doStuff();
    }

    private String doStuff() {
        // This method may be full of bugs
        . . .
        return someString;
    }

}
 

In onze test willen we de afhankelijkheid van Collaborator en zijn bugs verbreken, dus we gaan Collaborator bespotten:

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

Stoppende ongeldige methoden

void methoden kunnen worden samengevoegd met de doThrow () , doAnswer () , doNothing () , doCallRealMethod () familie van methoden.

Runnable mock = mock(Runnable.class);

doThrow(new UnsupportedOperationException()).when(mock).run();

mock.run(); // throws the UnsupportedOperationException
 

Merk op dat void methoden niet kunnen worden weggegooid when(..) ervoor zorgen dat de compiler void methoden niet als argument gebruiken.

Mockito-annotaties gebruiken

De les die we gaan testen is:

public class Service{

    private Collaborator collaborator;

    public Service(Collaborator collaborator){
        this.collaborator = collaborator;
    }
    
    
    public String performService(String input){
        return collaborator.transformString(input);
    }
}
 

De medewerker is:

public class Collaborator {

    public String transformString(String input){
        return doStuff();
    }

    private String doStuff()
    {
        // This method may be full of bugs
        . . .
        return someString;
    }

}
 

In onze test willen we de afhankelijkheid van Collaborator en zijn bugs verbreken, dus we gaan Collaborator bespotten. Het gebruik van @Mock annotatie is een handige manier om voor elke test verschillende instanties van @Mock te maken:

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 zal proberen de afhankelijkheidsinjectie in de volgende volgorde op te lossen:

  1. Op constructeur gebaseerde injectie - mocks worden in de constructor geïnjecteerd met de meeste argumenten (als sommige argumenten niet kunnen worden gevonden, worden nulwaarden doorgegeven). Als een object met succes is gemaakt via de constructor, worden er geen andere strategieën toegepast.
  2. Setter-gebaseerde injectie - mocks worden geïnjecteerd per type. Als er meerdere eigenschappen van hetzelfde type zijn, worden de namen van eigenschappen en onechte namen aan elkaar gekoppeld.
  3. Directe veldinjectie - hetzelfde als voor op setter gebaseerde injectie.

Merk op dat er geen falen wordt gerapporteerd in het geval dat een van de bovengenoemde strategieën faalde.

Raadpleeg de nieuwste @InjectMocks voor meer informatie over dit mechanisme in de nieuwste versie van Mockito.

Controleer methode-aanroepen van bespot object

Om te controleren of een methode is aangeroepen op een bespot object, kunt u de methode Mockito.verify gebruiken:

Mockito.verify(someMock).bla();
 

In dit voorbeeld beweren we dat de methode bla werd aangeroepen voor het someMock mock-object.

U kunt ook controleren of een methode met bepaalde parameters is aangeroepen:

Mockito.verify(someMock).bla("param 1");
 

Als u wilt controleren of een methode niet is aangeroepen, kunt u een extra VerificationMode parameter doorgeven om te verify :

Mockito.verify(someMock, Mockito.times(0)).bla();
 

Dit werkt ook als u wilt controleren of deze methode meer dan eens werd aangeroepen (in dit geval controleren we of de methode bla 23 keer werd genoemd):

Mockito.verify(someMock, Mockito.times(23)).bla();
 

Dit zijn meer voorbeelden voor de parameter VerificationMode , die meer controle bieden over het aantal keren dat een methode moet worden aangeroepen:

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
 

Argumenten verifiëren met ArgumentCaptor

Gebruik de klasse ArgumentCaptor om argumenten te valideren voor methoden die een mock worden genoemd. Hiermee kunt u de argumenten uit uw testmethode halen en er beweringen over uitvoeren.

Dit voorbeeld test een methode die de naam van een gebruiker met een gegeven ID bijwerkt. De methode laadt de gebruiker, werkt het name attribuut bij met de gegeven waarde en slaat het daarna op. De test wil controleren of het argument dat is doorgegeven aan de save een User is met de juiste ID en naam.

// 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"));
    }
}
 

Argumenten verifiëren met ArgumentMatcher

Mockito biedt een Matcher<T> -interface samen met een abstracte ArgumentMatcher<T> -klasse om argumenten te verifiëren. Het gebruikt een andere benadering van dezelfde use-case dan de ArgumentCaptor . Bovendien kan de ArgumentMatcher ook worden gebruikt bij het bespotten. Beide use-cases maken gebruik van de Mockito.argThat() methode die een redelijk leesbare testcode biedt.

verify(someMock).someMethod(Mockito.argThat(new ArgumentMatcher<String>() {
       
    @Override
    public boolean matches(Object o) {
        return o instanceof String && !((String)o).isEmpty();
    }

});        
 

Van de JavaDocs van ArgumentMatcher:

Waarschuwing: wees redelijk met het gebruik van gecompliceerde argumentmatching, vooral aangepaste argumentmatchers, omdat dit de test minder leesbaar kan maken. Soms is het beter om equals () te implementeren voor argumenten die worden doorgegeven aan mocks (Mockito gebruikt natuurlijk equals () voor het matchen van argumenten). Dit kan de test schoner maken.