vendredi 25 septembre 2026

À propos

Contact

Tests

Stubs, mocks et spies : le vocabulaire des doublures de test enfin clair

Trois mots que l'on confond sans arrêt en revue de code. Une définition nette, appuyée sur un test PHPUnit WordPress concret, pour ne plus se tromper.

Par Clément Hadrot • 27 juillet 2020 • 4 min de lecture • Aucun commentaire
Stubs, mocks et spies : le vocabulaire des doublures de test enfin clair

Lors d’une revue de code, un développeur junior de l’équipe a commenté une ligne de test par « on mock l’appel API ici ». Le code ne faisait pourtant que retourner une valeur fixe, sans jamais vérifier que quoi que ce soit avait été appelé. Ce n’était pas un mock : c’était un stub. La confusion est fréquente, et elle n’est pas seulement une question de pédantisme — elle change la façon dont on écrit le test.

Ces trois notions répondent chacune à une question différente : que doit retourner cette dépendance (stub), a-t-elle bien été appelée comme prévu (mock), ou simplement a-t-elle été appelée, sans que cela conditionne la réussite du test (spy) ? Clarifier cette distinction évite d’écrire des tests qui vérifient la mauvaise chose.

Le stub : une valeur imposée, rien de plus

Un stub remplace une dépendance réelle par une version qui retourne toujours la même chose, sans aucune logique de vérification. Il sert à isoler le code testé d’une dépendance lente, coûteuse ou non déterministe — un appel réseau, une lecture disque, une horloge système.

class Test_Calcul_Livraison extends WP_UnitTestCase {

    public function test_frais_livraison_zone_lointaine(): void {
        $service_geo = $this->createStub(Service_Geolocalisation::class);
        $service_geo->method('distance_km')->willReturn(850);

        $calculateur = new Calculateur_Frais_Livraison($service_geo);

        $this->assertSame(24.90, $calculateur->calculer('75001'));
    }
}

Ici, peu importe combien de fois distance_km() est appelée, ni avec quels arguments : le test ne s’intéresse qu’au résultat final du calcul, pas au comportement du service de géolocalisation.

Le mock : une attente sur l’interaction elle-même

Un mock va plus loin : il fait échouer le test si l’appel attendu n’a pas lieu, ou n’a pas lieu avec les bons arguments. On l’utilise quand l’appel lui-même est la chose importante à vérifier — typiquement l’envoi d’un e-mail, l’écriture d’un log, ou le déclenchement d’un hook.

public function test_notification_envoyee_apres_commande(): void {
    $notificateur = $this->createMock(Service_Notification::class);
    $notificateur->expects($this->once())
        ->method('envoyer')
        ->with($this->equalTo('client@example.test'), $this->stringContains('confirmée'));

    $service_commande = new Service_Commande($notificateur);
    $service_commande->valider(42);
}
L'essentiel à retenir : Le stub retourne une valeur imposée ; Le mock vérifie qu'un appel a bien eu lieu ; Le spy observe sans changer le comportement réel

Si envoyer() n’est jamais appelé, ou l’est avec un mauvais destinataire, le test échoue immédiatement — même si le reste du comportement de valider() semble correct. C’est précisément l’inverse du stub : ici, l’interaction est l’assertion.

Le spy : observer sans imposer de contrainte

Le spy enregistre les appels réels sans en modifier le comportement, pour les inspecter après coup, sans pour autant faire échouer le test si l’appel n’a pas eu lieu dans un ordre précis. Dans un contexte WordPress, l’outil natif le plus proche du spy reste did_action() et le comptage d’appels de hook :

public function test_hook_declenche_apres_publication(): void {
    add_action('billet_publie', function () {});

    $this->factory()->post->create([
        'post_type'   => 'billet',
        'post_status' => 'publish',
    ]);

    $this->assertGreaterThan(0, did_action('billet_publie'));
}

Ce test observe qu’un hook s’est déclenché, sans imposer d’attente stricte sur le nombre exact d’appels ni sur les arguments — une distinction utile quand on veut simplement s’assurer qu’un comportement existe, sans figer une implémentation encore mouvante.

Un tableau pour trancher rapidement

DoublureQuestion poséeFait échouer le test si absent ?
StubQue dois-je retourner ?Non
MockCet appel a-t-il eu lieu comme prévu ?Oui
SpyQue s’est-il passé, pour information ?Non, sauf assertion explicite ensuite

Pourquoi cette distinction évite des tests fragiles

Transformer systématiquement des stubs en mocks est une erreur courante : elle fige des détails d’implémentation qui n’ont pas besoin de l’être. Si un test vérifie qu’une méthode a été appelée exactement une fois alors que seule la valeur de retour compte réellement, le moindre refactoring interne — même sans changement de comportement observable — fait échouer la suite. À l’inverse, utiliser un stub là où un mock serait nécessaire laisse passer de vraies régressions, comme l’e-mail de confirmation qui ne part jamais.

La règle que nous appliquons : un mock se justifie uniquement quand l’effet de bord est la fonctionnalité elle-même. Dans tous les autres cas, un stub suffit et rend le test plus robuste aux refactorings.

Notre verdict

Ce vocabulaire n’est pas un exercice académique : il structure la façon de penser un test. Se demander « est-ce que je vérifie une valeur ou une interaction ? » avant d’écrire la moindre ligne de simulation évite la moitié des tests trop rigides rencontrés en revue de code. Les bibliothèques dédiées comme Mockery ou Brain Monkey offrent des API plus riches pour ces trois usages, mais la distinction conceptuelle reste identique quel que soit l’outil choisi.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi