Pourquoi un test qui vérifie la sortie d’un shortcode devrait-il charger une base de données MySQL, initialiser l’ensemble des hooks du cœur et instancier l’objet $wp_query ? La réponse habituelle — « parce que WP_UnitTestCase est l’outil disponible » — n’est pas satisfaisante quand le shortcode en question ne fait rien d’autre que transformer des attributs en une chaîne de caractères.
La solution consiste à extraire la logique du shortcode dans une fonction pure, indépendante de l’environnement WordPress, et à ne réserver l’appel à add_shortcode() qu’à une fine couche d’intégration testée séparément, une seule fois.
Le piège du callback monolithique
La forme la plus courante d’un shortcode mélange trois responsabilités dans une seule fonction : lire les attributs, appliquer une logique métier (calcul, mise en forme, appel à une donnée), et produire le balisage HTML final. Tant que ces trois responsabilités restent imbriquées, tester le calcul oblige à passer par le rendu complet, donc par WordPress :
// Version monolithique, difficile à tester isolément
add_shortcode('compte_a_rebours', function (array $atts): string {
$atts = shortcode_atts(['date' => ''], $atts);
$cible = new DateTimeImmutable($atts['date']);
$jours = $cible->diff(new DateTimeImmutable())->days;
return sprintf('<span class="compte-a-rebours">%d jour(s)</span>', $jours);
});
Extraire la logique en fonction pure
La même fonctionnalité, découpée, sépare le calcul du rendu :
// Fonction pure, testable sans WordPress
function calculer_jours_restants(string $dateCible, DateTimeImmutable $maintenant): int
{
$cible = new DateTimeImmutable($dateCible);
return $maintenant->diff($cible)->days;
}
// Façade fine, appelée par add_shortcode
add_shortcode('compte_a_rebours', function (array $atts): string {
$atts = shortcode_atts(['date' => ''], $atts);
$jours = calculer_jours_restants($atts['date'], new DateTimeImmutable());
return sprintf('<span class="compte-a-rebours">%d jour(s)</span>', $jours);
});

La fonction calculer_jours_restants() ne dépend d’aucune fonction du cœur WordPress : elle peut être testée avec un simple PHPUnit\Framework\TestCase, sans étendre WP_UnitTestCase ni charger de base de données.
Le test unitaire correspondant
use PHPUnit\Framework\TestCase;
final class CompteARebourseTest extends TestCase
{
public function test_calcule_le_bon_nombre_de_jours(): void
{
$maintenant = new DateTimeImmutable('2024-01-01');
$jours = calculer_jours_restants('2024-01-10', $maintenant);
$this->assertSame(9, $jours);
}
public function test_renvoie_zero_pour_une_date_passee(): void
{
$maintenant = new DateTimeImmutable('2024-01-15');
$jours = calculer_jours_restants('2024-01-01', $maintenant);
$this->assertSame(0, $jours);
}
}
Ce test s’exécute en quelques millisecondes, sans dépendance externe, et peut tourner dans une étape de CI distincte de la suite d’intégration, ce qui permet de le lancer à chaque sauvegarde de fichier pendant le développement.
Ce qui mérite encore un test d’intégration
Extraire la logique métier ne dispense pas totalement de WP_UnitTestCase : il reste utile de vérifier, au moins une fois, que le shortcode est bien enregistré et que do_shortcode('[compte_a_rebours date="2024-12-25"]') produit un balisage cohérent avec les attributs par défaut de shortcode_atts(). Ce test d’intégration reste court, car il ne vérifie que le branchement, pas le calcul lui-même, déjà couvert par les tests unitaires purs.
Répartir les responsabilités entre les deux suites
- Tests unitaires purs : toutes les variantes de calcul, y compris les cas limites (date invalide, date déjà passée, fuseau horaire).
- Test d’intégration WordPress : un seul cas nominal, pour vérifier l’enregistrement du shortcode et l’échappement correct du HTML produit.
En résumé
Découper un shortcode en une fonction pure et une façade d’enregistrement permet de tester l’essentiel de sa logique sans jamais démarrer l’environnement WordPress. Le gain en vitesse d’exécution encourage à multiplier les cas limites testés, ce qu’un test d’intégration lourd décourage naturellement par sa durée.