# Mockito Quick Start Tutoriel

# Les concepts de base

Mockito Photo by Hessam Hojati on Unsplash

Mockito est une librairie qui permet de créer des tests doubles (mock, spy, stub) dans ses tests unitaires puis de vérifier leur comportement. Mockito s'intègre très bien avec les différents frameworks de tests unitaires et avec JUnit en particulier.

L'intérêt des tests doubles apparaît rapidement lorsque le comportement de la classe à tester est dépendant d'autres classes, que ce soit des services, des DAO ou autres fournisseurs de ressources. Dans ce cas, pour isoler le comportement de la classe à tester du comportement de ses dépendances, le plus simple est encore de les mocker: c'est à dire d'injecter comme dépendance des objets dont le comportement est contrôlé.

Mockito défini 3 types de tests doubles:

  • Mock: les mocks sont créés directement à partir de la classe et le comportement de chaque méthode du mock peut être programmé ou non.
  • Spy: les spy sont créés à partir d'un objet déjà existant. Le spy se comporte par défaut comme son objet, mais ce comportement peut être modifié au besoin.
  • Partial Mock: les mocks partiels sont fortement déconseillés. Tous comme les mocks, ils sont créés à partir de la classe, mais il est possible de forcer l'exécution du code réel.

Pour chaque test double, Mockito offre plusieurs possibilités:

  • Stubbing: les méthodes des mock ou des spy peuvent être reprogrammées pour choisir la valeur à retourner ou pour lever une exception en fonction des arguments qui sont passés lors de l'appel.
  • Vérification des interactions: Mockito permet de vérifier si les méthodes d'un mock ou d'un spy ont été appelées, combien de fois, dans quel ordre et avec quels arguments.

# Installation avec Maven

Pour commencer, il faut inclure Mockito comme dépendance de test dans la configuration maven (pensez à vérifier la dernière version sur mvnrepository):

<dependencies>
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>2.28.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>
1
2
3
4
5
6
7
8

L'ensemble des fonctionnalités sont ensuite disponibles sous forme de méthodes statiques au niveau de la classe org.mockito.Mockito. Le plus simple est encore de les ajouter dans le test via un import statique:

import static org.mockito.Mockito.*;
1

# Mocking

Pour créer un mock, il suffit d'utiliser Mockito.mock(SomeClass.class). Par défaut, les méthodes de l'objet obtenu vont renvoyer une valeur: soit null ou une valeur de primitive (par exemple 0 pour les int/Integer, false pour les boolean/Boolean) ou une collection vide.



 


@Test
public void mock() {
  Country country = Mockito.mock(Country.class);
  assertThat(country.getName()).isNull();
}
1
2
3
4

# Spying

La création d'un spy est tout aussi simple, cette fois c'est la méthode Mockito.spy(someObject) qui est utilisée. Le spy obtenu continu de garder le comportement de l'objet tant qu'aucun stub n'a été défini.



 


@Test
public void spy() {
  Country spy = Mockito.spy(Country.USA);
  assertThat(spy.getName()).isEqualTo("United States of America");
}
1
2
3
4

# Stubbing

Le stubbing permet de définir le comportement attendu sur les méthodes d'un mock ou d'un spy. Pour cela, on utilise Mockito.when(someMockedObject.methodToStub()):




 

 

 


@Test
public void stubbing() {
  Country country = Mockito.mock(Country.class);
  when(country.getName()).thenReturn("France");
  assertThat(country.getName()).isEqualTo("France");
  when(country.getName()).thenAnswer(invocationOnMock -> "France");
  assertThat(country.getName()).isEqualTo("France");
  when(country.getName()).thenThrow(new NullPointerException());
  assertThrows(NullPointerException.class, () -> country.getName());
}
1
2
3
4
5
6
7
8
9

La méthode thenReturn défini directement la valeur à retourner lorsque le stub est appelé. La méthode thenAnswer, en revanche, défini dynamiquement cette valeur en fonction du contexte (par exemple les arguments; ce context étant passé via un objet de la classe org.mockito.invocation.InvocationOnMock).

TIP

Dans l'exemple ci-dessus, la méthode getName a été reprogrammée plusieurs fois. Dans ces cas, c'est toujours le dernier stub qui prends le pas sur les précédents.

# Correspondance des arguments

Il est également possible de restreindre un stub en fonction des arguments passés lors de l'appel. Cela permet de rendre le test plus explicite en indiquant comment le stub est utilisé. Mockito introduit la notion d'ArgumentMatcher pour valider dynamiquement si un stub s'applique ou non suivant les arguments passés en paramètre.








 



 
 




@Test
public void stubbingWithArgumentMatcher() {
  Countries countries = Mockito.spy(new Countries());
  when(countries.contains(Country.USA)).thenReturn(true);
  assertThat(countries.contains(Country.USA)).isTrue();
  assertThat(countries.contains(Country.FRANCE)).isFalse();
  reset(countries);
  when(countries.contains(any(Country.class))).thenReturn(true);
  assertThat(countries.contains(Country.USA)).isTrue();
  assertThat(countries.contains(Country.FRANCE)).isTrue();
  reset(countries);
  ArgumentMatcher<Country> match = country -> country.getDensity() >= Country.FRANCE.getDensity();
  when(countries.contains(argThat(match))).thenReturn(true);
  assertThat(countries.contains(Country.USA)).isFalse();
  assertThat(countries.contains(Country.FRANCE)).isTrue();
  assertThat(countries.contains(Country.CHINA)).isTrue();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

TIP

La méthode statique Mockito.reset() permet de réinitialiser un mock ou un spy dans son état initial. Dans l'exemple ci-dessus, cette méthode est utilisé pour changer le comportement du mock au cours du test en repartant de zéro.

# Appels successifs

Lorsqu'un stub a vocation à être appelé plusieurs fois, il est possible de changer la valeur retournée à chaque appel. Soit en spécifiant plusieurs valeurs directement dans la méthode thenReturn, soit en chaînant les thenReturn, thenAnswer et thenThrow:




 



 



@Test
public void stubbingSuccessiveCalls() {
  Countries countries = Mockito.spy(new Countries());
  when(countries.contains(Country.USA)).thenReturn(true, false, true);
  assertThat(countries.contains(Country.USA)).isTrue();
  assertThat(countries.contains(Country.USA)).isFalse();
  assertThat(countries.contains(Country.USA)).isTrue();
  when(countries.contains(Country.USA)).thenReturn(true).thenThrow(new NullPointerException());
  assertThat(countries.contains(Country.USA)).isTrue();
  assertThrows(NullPointerException.class, () -> countries.contains(Country.USA));
}
1
2
3
4
5
6
7
8
9
10

Si le stub est appelé plus que le nombre de valeur de retour défini, la dernière valeur définie est utilisée pour les appels suivants.

WARNING

Pour définir le comportement lors d'appels successifs, il ne faut pas redéfinir le stub complétement (en repassant par when) puisque lorsque le stub est redéfini, le comportement précédent est écrasé.

# Methode (void)

Il est également possible de créer un stub sur les méthodes void: soit pour indiquer à un spy de ne rien faire (doNothing()) lorsqu'une de ces méthodes est appelée (plutôt que d'appeler le code réel), soit pour demander de lever une exception doThrow():




 



 


@Test
public void stubbingVoidMethod() {
  Countries countries = Mockito.spy(new Countries());
  doNothing().when(countries).add(Country.USA);
  countries.add(Country.FRANCE);
  countries.add(Country.USA);
  assertThat(countries.size()).isEqualTo(1);
  doThrow(new NullPointerException()).when(countries).add(Country.USA);
  assertThrows(NullPointerException.class, () -> countries.add(Country.USA));
}
1
2
3
4
5
6
7
8
9

# Verification

Les mock et les spy permettent de contrôler les dépendances injectées dans la classe testée. Comme le code exécuté est factice, il est intéressant de vérifier si et comment la classe interagit avec ces dépendances. Par exemple, pour vérifier qu'un service appelle bien la méthode de mise à jour des données d'un DAO mocké et ce avec le bon argument.

# Verifier le nombre d'appel

Mockito.verify vérifie que le stub d'un mock ou un spy est appelé (ou non). Mockito offre différent helper pour s'assurer du nombre d'interaction, de manière fixe ou bornée:

@Test
public void verifyInteraction() {
  Country spy = Mockito.spy(Country.USA);
  verify(spy, never()).getName();
  assertThat(spy.getName()).isEqualTo("United States of America");
  verify(spy).getName();
  verify(spy, atMostOnce()).getName();
  verify(spy, atLeastOnce()).getName();
  when(spy.getName()).thenReturn("France");
  assertThat(spy.getName()).isEqualTo("France");
  verify(spy, times(2)).getName();
  verify(spy, atLeast(1)).getName();
  verify(spy, atMost(3)).getName();
  verify(spy, never()).getPopulation();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Verifier les arguments utilisés lors de l'appel

De la même manière que les ArgumentMatcher sont utilisés pour controller l'execution d'un stub, ils peuvent être utilisés pour vérifier ses interactions. Là encore, il s'agit d'expliciter dans le test comment le mock est utilisé:





 
 
 

@Test
public void verifyWithArgumentMatcher() {
  Countries countries = Mockito.spy(new Countries());
  assertThat(countries.contains(Country.FRANCE)).isFalse();
  ArgumentMatcher<Country> matcher =
      country -> country.getDensity() >= Country.FRANCE.getDensity();
  verify(countries).contains(argThat(matcher));
}
1
2
3
4
5
6
7

# Vérifier en capturant les arguments

En plus de vérifier comment le mock a interagit, il est possible, lors de cette vérification, de capturer les arguments reçus par le mock au moment de l'appel. Mockito utilise le concept d'ArgumentCaptor pour récupérer ces arguments. Une fois capturés, il devient possible de les tester:





 
 


@Test
public void verifyWithArgumentCaptor() {
  Countries countries = Mockito.spy(new Countries());
  assertThat(countries.contains(Country.FRANCE)).isFalse();
  ArgumentCaptor<Country> argument = ArgumentCaptor.forClass(Country.class);
  verify(countries).contains(argument.capture());
  assertThat(argument.getValue().getName()).isEqualTo("France");
}
1
2
3
4
5
6
7

# Vérification à la mode Behavior Driven Development

Mockito fournit également des helpers orientés BDD via la classe org.mockito.BDDMockito. Le principal changement est le nom des méthodes qui offrent une sémantique de type given, when, then:




 

 

@Test
public void verifyBdd() {
  Country mock = Mockito.mock(Country.class);
  given(mock.getName()).willReturn("France");
  assertThat(mock.getName()).isEqualTo("France");
  then(mock).should(times(1)).getName();
}
1
2
3
4
5
6

# Autres fonctionnalités

Mockito propose d'autres fonctionnalités qui ne sont pas exposées dans ce tuto: un jeu d'annotations (@Mock, @Spy, @Captor, @InjectMocks), la vérification de l'ordre des interactions (inOrder()), le mock de classe abstraite, le mock de classes et méthodes finales ou encore le mock Partiel. RTFM en buvant des mojitos 😄

Dernière mise à jour: 7/2/2019, 12:26:00 AM