Deux extensions maintenues par la même équipe communiquent par un hook d’action personnalisé : la première publie un événement via do_action(), la seconde s’y accroche via add_action() pour réagir. Ce mécanisme fonctionne parfaitement tant que la forme des données transmises reste stable ; il se rompt silencieusement dès qu’une des deux extensions évolue sans que l’autre en soit informée.
Ce sujet ne traite pas du test contractuel d’une réponse REST, qui répond à une problématique voisine mais distincte : ici, l’interface concernée est un hook d’action interne, pas un point de terminaison HTTP exposé.
Le contrat implicite d’un hook partagé
Une extension de gestion de réservations publie un événement à chaque confirmation, consommé par une seconde extension chargée d’envoyer une notification :
// Extension A : publication de l'événement
do_action('reservation_confirmee', [
'id_reservation' => $reservation->id,
'email_client' => $reservation->email,
'date_creneau' => $reservation->creneau->format('Y-m-d H:i'),
]);
// Extension B : consommation de l'événement
add_action('reservation_confirmee', function (array $donnees): void {
envoyer_notification($donnees['email_client'], $donnees['date_creneau']);
});
Rien, dans ce code, ne documente formellement que le tableau doit contenir exactement ces trois clés, avec ces types précis. Un renommage de email_client en email dans l’extension A, effectué sans concertation, casse silencieusement l’extension B : aucune erreur PHP ne survient, la clé attendue est simplement absente, et envoyer_notification() reçoit une valeur nulle sans avertissement.
Le schéma du contrat, en arborescence
reservation_confirmee (hook d'action)
├── id_reservation : int
├── email_client : string (adresse e-mail valide)
└── date_creneau : string (format Y-m-d H:i)

Le test qui fige ce contrat
Le test s’accroche lui-même au hook, capture les données réellement transmises lors d’une action déclenchée en conditions de test, puis vérifie leur forme exacte :
class ContratReservationConfirmeeTest extends WP_UnitTestCase
{
public function test_le_hook_transmet_le_format_attendu(): void
{
$donneesCapturees = null;
add_action('reservation_confirmee', function (array $donnees) use (&$donneesCapturees): void {
$donneesCapturees = $donnees;
});
declencher_confirmation_de_test();
$this->assertIsArray($donneesCapturees);
$this->assertArrayHasKey('id_reservation', $donneesCapturees);
$this->assertIsInt($donneesCapturees['id_reservation']);
$this->assertArrayHasKey('email_client', $donneesCapturees);
$this->assertMatchesRegularExpression(
'/^[^@\s]+@[^@\s]+\.[^@\s]+$/',
$donneesCapturees['email_client']
);
$this->assertArrayHasKey('date_creneau', $donneesCapturees);
$this->assertMatchesRegularExpression(
'/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/',
$donneesCapturees['date_creneau']
);
}
}
Ce test appartient à l’extension qui publie l’événement, l’extension A : c’est elle qui prend l’engagement de respecter ce format, et c’est donc à elle de le vérifier à chaque modification de son propre code, avant même que l’extension B ne soit sollicitée pour vérifier quoi que ce soit.
Où placer ce test dans l’architecture du projet
- Le test de contrat vit dans le dépôt de l’extension productrice de l’événement, pas dans celui de l’extension consommatrice.
- Toute modification de la structure des données publiées doit obligatoirement s’accompagner d’une modification de ce test, ce qui rend le changement visible dans la revue de code plutôt que silencieux.
- Un changement volontaire de format s’accompagne d’une communication explicite vers l’équipe qui maintient l’extension consommatrice, le test de contrat servant alors de preuve écrite du nouveau format à adopter de part et d’autre.
En résumé
Un hook d’action partagé entre deux extensions constitue un contrat de données aussi engageant qu’une interface REST documentée, même s’il ne prend jamais cette forme visible. Un test qui capture et vérifie la structure exacte des données transmises transforme ce contrat implicite en garantie automatisée, et rend visible, dans la revue de code, toute rupture qui serait autrement passée inaperçue jusqu’à ce qu’un client signale une notification jamais reçue.