Un correctif introduit par erreur un point-virgule en trop après un add_filter(), transformant l’appel en instruction morte et le filtre en simple commentaire silencieux. Le code compilait, aucune erreur PHP ne remontait, et le filtre censé arrondir les prix affichés ne s’exécutait tout simplement jamais. Un test qui vérifie qu’un hook est bien enregistré et bien déclenché aurait empêché ce genre de régression silencieuse de passer en revue de code.
WordPress expose trois fonctions simples pour ce genre de vérification : has_action() et has_filter() pour confirmer qu’un callback est accroché à un hook, et did_action() pour compter combien de fois une action donnée s’est réellement déclenchée pendant l’exécution. Combinées à des assertions classiques, elles couvrent l’essentiel des besoins sans dépendance externe.
Vérifier qu’un callback est bien enregistré
La première question à se poser n’est pas « le filtre fonctionne-t-il ? » mais « le filtre est-il seulement accroché ? ». C’est le rôle de has_filter() et has_action() :
class Test_Hooks_Catalogue extends WP_UnitTestCase {
public function test_le_filtre_de_prix_est_enregistre() {
$priorite = has_filter( 'mon_plugin_prix_affiche', 'mon_plugin_arrondir_prix' );
$this->assertNotFalse( $priorite );
$this->assertSame( 20, $priorite );
}
}
has_filter() retourne la priorité du callback, ou false s’il n’est pas enregistré. Vérifier la priorité elle-même a du sens quand l’ordre d’exécution compte, par exemple si deux extensions modifient la même valeur et que l’ordre change le résultat final.
Compter les déclenchements réels avec did_action
Savoir qu’un callback est accroché ne dit rien sur son exécution effective. did_action() retourne le nombre de fois qu’une action a été déclenchée depuis le début de la requête, ce qui permet des assertions précises :
public function test_action_apres_creation_de_commande() {
$this->assertSame( 0, did_action( 'mon_plugin_commande_creee' ) );
mon_plugin_creer_commande( array( 'total' => 42.00 ) );
$this->assertSame( 1, did_action( 'mon_plugin_commande_creee' ) );
}

Ce comptage est particulièrement utile pour détecter les déclenchements en double, un bug fréquent quand un hook est accroché à la fois sur save_post et sur un hook spécifique comme save_post_produit sans garde-fou.
Vérifier l’effet réel d’un filtre, pas seulement sa présence
La vérification la plus robuste reste d’appeler apply_filters() directement et de comparer l’entrée à la sortie, sans se soucier de savoir combien de callbacks sont accrochés :
public function test_le_prix_est_arrondi_au_centime_superieur() {
$prix_arrondi = apply_filters( 'mon_plugin_prix_affiche', 19.994 );
$this->assertSame( 20.00, $prix_arrondi );
}
Cette approche a un avantage : elle reste valide même si l’implémentation interne change de nom de fonction, tant que le contrat du filtre — sa signature d’entrée et de sortie — reste stable. C’est généralement le niveau de granularité le plus pertinent à tester en priorité.
Tester le retrait d’un hook
Un cas fréquent sur les projets avec plusieurs extensions maison : vérifier qu’une extension désactive correctement un comportement du cœur ou d’une autre extension via remove_action() :
- Vérifier avant retrait que
has_action()retourne bien une priorité - Appeler le code responsable du retrait
- Vérifier après retrait que
has_action()retournefalse
Sans ce test, un changement de priorité par défaut dans une mise à jour du cœur ou d’une extension tierce peut faire échouer silencieusement le remove_action(), puisque WordPress exige que la priorité passée corresponde exactement à celle utilisée lors de l’enregistrement.
En résumé
Avant de sortir l’artillerie lourde pour simuler des fonctions du cœur, ces trois fonctions natives couvrent déjà la majorité des besoins de test sur les hooks : présence avec has_filter()/has_action(), fréquence de déclenchement avec did_action(), et effet réel avec apply_filters(). Les bibliothèques de mock comme Brain Monkey ont leur place, mais pour un autre usage : tester du code qui appelle des fonctions WordPress sans charger le cœur du tout.