Une action AJAX qui mettait à jour le statut d’une commande depuis l’écran d’administration fonctionnait bien en manuel, mais un correctif ultérieur avait fait sauter la vérification de nonce sans que personne ne s’en rende compte : la fonction utilisait encore check_ajax_referer(), mais avec un nom d’action qui ne correspondait plus à celui généré côté JavaScript. Résultat, l’action AJAX renvoyait une erreur pour tous les utilisateurs, y compris légitimes, en production. Un test d’intégration ciblé sur cette action aurait détecté l’incohérence en quelques secondes.
WordPress fournit une classe de test dédiée, WP_Ajax_UnitTestCase, qui simule un appel à admin-ajax.php sans navigateur ni serveur, en interceptant la sortie qu’une action AJAX enverrait normalement avec wp_die().
Pourquoi une classe de test dédiée aux actions AJAX
Une action AJAX se termine presque toujours par un appel à wp_die(), directement ou via wp_send_json(). Dans un contexte de test classique, cet appel interromprait immédiatement l’exécution du test lui-même. WP_Ajax_UnitTestCase remplace ce comportement par une exception dédiée, WPAjaxDieContinueException, capturée automatiquement pour permettre au test de continuer et d’inspecter la sortie générée.
Écrire le test
class Test_Ajax_Statut_Commande extends WP_Ajax_UnitTestCase {
public function test_met_a_jour_le_statut_pour_un_administrateur() {
$admin_id = self::factory()->user->create( array( 'role' => 'administrator' ) );
$commande_id = self::factory()->post->create( array( 'post_type' => 'commande' ) );
wp_set_current_user( $admin_id );
$_POST['commande_id'] = $commande_id;
$_POST['statut'] = 'expediee';
$_POST['_ajax_nonce'] = wp_create_nonce( 'maj_statut_commande' );
try {
$this->_handleAjax( 'maj_statut_commande' );
} catch ( WPAjaxDieContinueException $e ) {
// Comportement attendu : wp_die() a été intercepté.
}
$reponse = json_decode( $this->_last_response, true );
$this->assertTrue( $reponse['success'] );
}
}

La propriété $this->_last_response contient exactement ce que wp_send_json() aurait envoyé au navigateur, prêt à être décodé et vérifié avec des assertions classiques sur la structure JSON.
Tester le rejet pour nonce invalide
Le scénario symétrique, tout aussi important, consiste à vérifier qu’une requête sans nonce valide échoue proprement :
public function test_rejette_sans_nonce_valide() {
$admin_id = self::factory()->user->create( array( 'role' => 'administrator' ) );
wp_set_current_user( $admin_id );
$_POST['commande_id'] = 1;
$_POST['_ajax_nonce'] = 'nonce-invalide';
$this->setExpectedException( 'WPAjaxDieStopException' );
$this->_handleAjax( 'maj_statut_commande' );
}
Notez la différence de classe d’exception : WPAjaxDieStopException signale un arrêt réel (mort du script), contrairement à WPAjaxDieContinueException utilisée pour la sortie JSON normale. Les deux servent à des scénarios de test distincts.
Ne pas oublier le contrôle de capacité
Un nonce valide prouve que la requête vient bien du bon formulaire, pas que l’utilisateur a le droit d’agir. Les deux vérifications sont indépendantes et doivent être testées séparément :
- Un abonné avec un nonce valide doit être rejeté par
current_user_can() - Un administrateur avec un nonce invalide doit être rejeté par
check_ajax_referer() - Seule la combinaison des deux doit aboutir à un succès
En résumé
WP_Ajax_UnitTestCase permet de couvrir des actions admin-ajax entières sans navigateur, en interceptant proprement le wp_die() final et en donnant accès à la réponse brute. Le raisonnement est proche de celui des routes REST personnalisées, mais admin-ajax reste un mécanisme distinct, plus ancien, avec ses propres classes de test et ses propres pièges de nonce.