Introduction
Mockito est un puissant framework Java utilisé pour le mock et les tests unitaires d'applications Java. Dans ce laboratoire (LabEx), nous allons explorer le concept des spies dans Mockito. Un Spy est un outil de test unique qui agit comme un mock partiel - il suit les interactions avec un objet tout en permettant toujours d'appeler les méthodes réelles.
Ce comportement double rend les spies particulièrement utiles lorsque nous voulons vérifier les interactions avec des objets tout en ayant besoin que la fonctionnalité réelle s'exécute. Tout au long de ce laboratoire, nous apprendrons à créer des spies en utilisant différentes approches, à comprendre comment configurer le comportement d'un spy et à comparer les spies avec les mocks pour mettre en évidence leurs principales différences.
Création d'un projet Java et compréhension du Spy Mockito
Dans cette étape, nous allons configurer la structure de notre projet et comprendre ce qu'est un Spy dans Mockito. Les spies nous permettent de suivre les appels de méthodes sur des objets réels tout en utilisant leur implémentation réelle.
Créer le fichier Java
Commençons par créer notre fichier Java principal pour ce laboratoire (LabEx). Dans l'IDE Web, créez un nouveau fichier appelé MockitoSpyDemo.java dans le répertoire ~/project.

Ajoutez le code suivant pour importer les dépendances nécessaires :
import org.junit.Test;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.MockitoAnnotations;
import org.mockito.exceptions.misusing.NotAMockException;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;
public class MockitoSpyDemo {
public static void main(String[] args) {
System.out.println("This is a Mockito Spy demonstration.");
System.out.println("To run the tests, you need to use JUnit.");
}
}
Compréhension du Spy Mockito
Un Spy dans Mockito est un wrapper autour d'un objet réel qui vous permet de :
- Suivre toutes les interactions avec l'objet (comme un mock)
- Exécuter les méthodes réelles de l'objet (contrairement à un mock)
Imaginez un spy comme un observateur qui regarde ce qui se passe à un objet sans interférer avec son comportement normal, sauf s'il est spécifiquement programmé pour le faire.
Ajoutons notre première méthode de test pour démontrer l'utilisation de base d'un spy. Ajoutez la méthode suivante à votre classe MockitoSpyDemo :
@Test
public void basicSpyExample() {
// Create a list and spy on it
List<String> realList = new ArrayList<>();
List<String> spyList = Mockito.spy(realList);
// Using the spy to add an item (the real method is called)
spyList.add("Hello");
// Verify the interaction happened
Mockito.verify(spyList).add("Hello");
// The real method was called, so the list actually has the item
assertEquals(1, spyList.size());
assertEquals("Hello", spyList.get(0));
System.out.println("Basic Spy Example - Spy List Content: " + spyList);
System.out.println("Basic Spy Example - Spy List Size: " + spyList.size());
}
Compilons et exécutons notre code pour voir s'il fonctionne :
cd ~/project
javac -cp lib/junit.jar:lib/mockito-core.jar:lib/byte-buddy.jar:lib/byte-buddy-agent.jar:lib/objenesis.jar:lib/hamcrest-core.jar MockitoSpyDemo.java
java -cp .:lib/junit.jar:lib/mockito-core.jar:lib/byte-buddy.jar:lib/byte-buddy-agent.jar:lib/objenesis.jar:lib/hamcrest-core.jar MockitoSpyDemo

Vous devriez voir la sortie suivante :
This is a Mockito Spy demonstration.
To run the tests, you need to use JUnit.
Notre méthode principale s'exécute correctement. Cependant, pour exécuter les tests, nous devons utiliser JUnit. Au lieu de configurer un exécuteur JUnit complexe pour ce laboratoire, nous allons nous concentrer sur la compréhension de la structure du code et des concepts.
Dans l'étape suivante, nous explorerons différentes façons de créer des spies dans Mockito.
Différentes façons de créer des spies dans Mockito
Dans cette étape, nous allons explorer différentes approches pour créer des spies dans Mockito. Nous apprendrons à créer des spies en utilisant la méthode Mockito.spy() et l'annotation @Spy.
Utilisation de la méthode Mockito.spy()
La façon la plus directe de créer un spy est d'utiliser la méthode Mockito.spy(). Ajoutons une méthode de test qui démontre cette approche :
@Test
public void testSpyWithMockitoSpyMethod() {
// Create an ArrayList and a spy of it
ArrayList<Integer> realList = new ArrayList<>();
ArrayList<Integer> spyList = Mockito.spy(realList);
// Adding elements to the spy (real methods are called)
spyList.add(5);
spyList.add(10);
spyList.add(15);
// Verifying interactions
Mockito.verify(spyList).add(5);
Mockito.verify(spyList).add(10);
Mockito.verify(spyList).add(15);
// Verifying that elements were actually added to the list
assertEquals(3, spyList.size());
System.out.println("Spy Method Example - Spy List Content: " + spyList);
System.out.println("Spy Method Example - Spy List Size: " + spyList.size());
}
Utilisation de l'annotation @Spy
Une autre façon de créer un spy est d'utiliser l'annotation @Spy. Cette approche est particulièrement utile lorsque vous souhaitez initialiser automatiquement les spies. Ajoutez le code suivant à votre classe MockitoSpyDemo :
// Declare a spy using annotation
@Spy
ArrayList<Integer> annotationSpyList = new ArrayList<>();
@Before
public void initSpies() {
// Initialize mocks and spies with annotations
MockitoAnnotations.initMocks(this);
}
@Test
public void testSpyWithAnnotation() {
// Adding elements to the spy
annotationSpyList.add(5);
annotationSpyList.add(10);
annotationSpyList.add(15);
// Verifying interactions
Mockito.verify(annotationSpyList).add(5);
Mockito.verify(annotationSpyList).add(10);
Mockito.verify(annotationSpyList).add(15);
// Verifying that elements were actually added to the list
assertEquals(3, annotationSpyList.size());
System.out.println("Annotation Spy Example - Spy List Content: " + annotationSpyList);
System.out.println("Annotation Spy Example - Spy List Size: " + annotationSpyList.size());
}
Comprendre la différence entre les approches
Les deux approches créent un spy, mais elles ont des cas d'utilisation différents :
Mockito.spy()est plus flexible et peut être utilisé n'importe où dans vos méthodes de test.- L'annotation
@Spyest plus pratique lorsque vous avez de nombreux spies et que vous souhaitez les initialiser tous d'un coup.
La méthode MockitoAnnotations.initMocks(this) est responsable de l'initialisation de tous les champs annotés. Dans les projets réels, vous pourriez utiliser @RunWith(MockitoJUnitRunner.class) pour que cette initialisation soit effectuée automatiquement.
Compilons notre code mis à jour :
cd ~/project
javac -cp lib/junit.jar:lib/mockito-core.jar:lib/byte-buddy.jar:lib/byte-buddy-agent.jar:lib/objenesis.jar:lib/hamcrest-core.jar MockitoSpyDemo.java
Le code devrait compiler sans erreur. Nos méthodes de test sont maintenant prêtes, mais comme mentionné précédemment, nous aurions besoin d'un exécuteur JUnit pour les exécuter automatiquement.
Dans l'étape suivante, nous apprendrons à configurer (stub) les spies pour remplacer leur comportement par défaut.
Simulation (stubbing) de spies et gestion des exceptions
Dans cette étape, nous apprendrons à configurer (stub) un spy pour remplacer son comportement par défaut et à gérer les exceptions qui peuvent survenir lors de l'utilisation de Mockito.
Configuration (Stubbing) d'un spy
Les spies utilisent les méthodes des objets réels par défaut, mais parfois, nous souhaitons changer ce comportement à des fins de test. Cela s'appelle "configurer (stub)" le spy. Ajoutons une méthode de test qui montre comment configurer un spy :
@Test
public void testStubbingSpy() {
// Create a spy of ArrayList
ArrayList<Integer> spyList = Mockito.spy(new ArrayList<>());
// Add an element
spyList.add(5);
// Verify the element was added
Mockito.verify(spyList).add(5);
// By default, contains() should return true for element 5
assertTrue(spyList.contains(5));
System.out.println("Stubbing Example - Default behavior: spyList.contains(5) = " + spyList.contains(5));
// Stub the contains() method to always return false for the value 5
Mockito.doReturn(false).when(spyList).contains(5);
// Now contains() should return false, even though the element is in the list
assertFalse(spyList.contains(5));
System.out.println("Stubbing Example - After stubbing: spyList.contains(5) = " + spyList.contains(5));
// The element is still in the list (stubbing didn't change the list content)
assertEquals(1, spyList.size());
assertEquals(Integer.valueOf(5), spyList.get(0));
System.out.println("Stubbing Example - Spy List Content: " + spyList);
}
Note importante sur la configuration (stubbing)
Lors de la configuration des méthodes d'un spy, il est recommandé d'utiliser doReturn(), doThrow(), doAnswer(), etc., au lieu de when(). En effet, avec les spies, la syntaxe when().thenReturn() peut appeler la méthode réelle lors de la configuration, ce qui peut entraîner des effets secondaires inattendus.
Par exemple :
// This might call the real get() method, causing issues if index 10 doesn't exist
Mockito.when(spyList.get(10)).thenReturn(99); // May throw IndexOutOfBoundsException
// This is the correct way to stub a spy
Mockito.doReturn(99).when(spyList).get(10); // Safe, doesn't call the real method
Gestion de l'exception NotAMockException
Si vous essayez d'utiliser les méthodes de vérification de Mockito sur un objet normal (pas un mock ou un spy), vous obtiendrez une NotAMockException. Ajoutons une méthode de test pour démontrer cette exception :
@Test
public void testNotAMockException() {
try {
// Create a regular ArrayList (not a mock or spy)
ArrayList<Integer> regularList = new ArrayList<>();
regularList.add(5);
// Try to verify an interaction on a regular object
Mockito.verify(regularList).add(5);
fail("Expected NotAMockException was not thrown");
} catch (NotAMockException e) {
// Expected exception
System.out.println("NotAMockException Example - Caught expected exception: " + e.getMessage());
assertTrue(e.getMessage().contains("Argument passed to verify() is not a mock"));
}
}
Compilons notre code mis à jour :
cd ~/project
javac -cp lib/junit.jar:lib/mockito-core.jar:lib/byte-buddy.jar:lib/byte-buddy-agent.jar:lib/objenesis.jar:lib/hamcrest-core.jar MockitoSpyDemo.java
Le code devrait compiler sans erreur. Dans cette étape, nous avons appris à configurer les méthodes des spies pour remplacer leur comportement par défaut et à gérer l'exception NotAMockException qui se produit lorsque nous utilisons les méthodes de vérification sur des objets normaux.
Dans l'étape suivante, nous comparerons les mocks et les spies pour comprendre leurs principales différences.
Mock vs Spy : Comprendre les différences
Dans cette étape, nous comparerons directement les mocks et les spies pour comprendre leurs principales différences. Cette comparaison vous aidera à choisir l'outil adapté à vos besoins de test.
Création d'un test de comparaison
Ajoutons une méthode de test qui démontre les différences entre les mocks et les spies :
@Test
public void testMockVsSpyDifference() {
// Create a mock and a spy of ArrayList
ArrayList<Integer> mockList = Mockito.mock(ArrayList.class);
ArrayList<Integer> spyList = Mockito.spy(new ArrayList<>());
// Add elements to both
mockList.add(1);
spyList.add(1);
// Verify interactions (both will pass)
Mockito.verify(mockList).add(1);
Mockito.verify(spyList).add(1);
// Check the size of both lists
System.out.println("Mock vs Spy - Mock List Size: " + mockList.size());
System.out.println("Mock vs Spy - Spy List Size: " + spyList.size());
// Mock returns default values (0 for size) unless stubbed
assertEquals(0, mockList.size());
// Spy uses real method implementation, so size is 1
assertEquals(1, spyList.size());
// Stub both to return size 100
Mockito.when(mockList.size()).thenReturn(100);
Mockito.when(spyList.size()).thenReturn(100);
// Both should now return 100 for size
assertEquals(100, mockList.size());
assertEquals(100, spyList.size());
System.out.println("Mock vs Spy (After Stubbing) - Mock List Size: " + mockList.size());
System.out.println("Mock vs Spy (After Stubbing) - Spy List Size: " + spyList.size());
}
Comprendre les principales différences
Récapitulons les principales différences entre les mocks et les spies dans Mockito :
Comportement des méthodes :
- Mock : Toutes les méthodes retournent des valeurs par défaut (null, 0, false) à moins d'être configurées (stubbed)
- Spy : Les méthodes appellent l'implémentation réelle à moins d'être configurées (stubbed)
État de l'objet :
- Mock : Les opérations ne modifient pas l'état (par exemple, ajouter à une liste mock n'ajoute rien en réalité)
- Spy : Les opérations modifient l'état de l'objet comme un objet réel
Création :
- Mock : Créé à partir d'une classe, pas d'une instance (
Mockito.mock(ArrayList.class)) - Spy : Habituellement créé à partir d'une instance (
Mockito.spy(new ArrayList<>()))
- Mock : Créé à partir d'une classe, pas d'une instance (
Cas d'utilisation :
- Mock : Lorsque vous voulez contrôler complètement le comportement et que vous n'avez pas besoin des méthodes réelles
- Spy : Lorsque vous voulez utiliser les méthodes réelles mais que vous avez besoin de remplacer seulement certains comportements
Exemple pratique : Quand utiliser un spy
Ajoutons un autre test qui montre un cas d'utilisation courant des spies - configurer partiellement un objet complexe :
@Test
public void testWhenToUseSpy() {
// Create a complex data object that we want to partially mock
StringBuilder builder = new StringBuilder();
StringBuilder spyBuilder = Mockito.spy(builder);
// Use the real append method
spyBuilder.append("Hello");
spyBuilder.append(" World");
// Verify the real methods were called
Mockito.verify(spyBuilder).append("Hello");
Mockito.verify(spyBuilder).append(" World");
// The real methods modified the state
assertEquals("Hello World", spyBuilder.toString());
System.out.println("When to Use Spy - Content: " + spyBuilder.toString());
// Stub the toString() method to return something else
Mockito.when(spyBuilder.toString()).thenReturn("Stubbed String");
// Now toString() returns our stubbed value
assertEquals("Stubbed String", spyBuilder.toString());
System.out.println("When to Use Spy - After Stubbing toString(): " + spyBuilder.toString());
// But other methods still work normally
spyBuilder.append("!");
Mockito.verify(spyBuilder).append("!");
// toString() still returns our stubbed value
assertEquals("Stubbed String", spyBuilder.toString());
}
Compilons notre code mis à jour :
cd ~/project
javac -cp lib/junit.jar:lib/mockito-core.jar:lib/byte-buddy.jar:lib/byte-buddy-agent.jar:lib/objenesis.jar:lib/hamcrest-core.jar MockitoSpyDemo.java
Le code devrait compiler sans erreur. Dans cette étape, nous avons appris les principales différences entre les mocks et les spies, et quand utiliser chacun d'eux. Les mocks remplacent complètement le comportement de l'objet original, tandis que les spies conservent le comportement original mais vous permettent de remplacer des méthodes spécifiques si nécessaire.
Résumé
Dans ce laboratoire (lab), nous avons exploré le concept des spies dans Mockito, une fonctionnalité puissante pour les tests unitaires en Java. Voici les points clés que nous avons abordés :
Création de spies : Nous avons appris à créer des spies en utilisant à la fois la méthode
Mockito.spy()et l'annotation@Spy. La première est plus flexible, tandis que la deuxième est plus pratique lorsque vous avez de nombreux spies.Comportement des spies : Les spies suivent les appels de méthodes comme les mocks, mais exécutent les méthodes réelles par défaut. Cette double nature les rend particulièrement utiles pour tester des objets réels tout en conservant la capacité de vérifier les interactions.
Configuration (Stubbing) des spies : Nous avons découvert comment remplacer le comportement par défaut des méthodes des spies en utilisant
doReturn(),doThrow()et d'autres méthodes de configuration (stubbing). Cela nous permet de contrôler des comportements spécifiques tout en conservant les autres intacts.Gestion des exceptions : Nous avons exploré l'exception
NotAMockExceptionqui se produit lorsque l'on essaie d'utiliser les méthodes de vérification de Mockito sur des objets normaux, et comment la gérer correctement.Mock vs Spy : Nous avons comparé les mocks et les spies pour comprendre leurs principales différences. Les mocks remplacent complètement le comportement original par des réponses par défaut ou configurées (stubbed), tandis que les spies conservent le comportement original mais permettent de remplacer des méthodes spécifiques.
Les spies sont un outil puissant dans l'arsenal de Mockito, offrant un équilibre entre le contrôle total des mocks et le réalisme des objets réels. Ils sont particulièrement utiles lorsque :
- Vous voulez tester un objet réel mais avez besoin de vérifier certaines interactions
- Vous avez besoin de remplacer seulement des méthodes spécifiques tout en conservant le reste du comportement de l'objet intact
- Vous travaillez avec des objets complexes pour lesquels mocker toutes les méthodes serait impraticable
En comprenant quand et comment utiliser les spies, vous pouvez écrire des tests unitaires plus efficaces et maintenables pour vos applications Java.



