Un bloc Gutenberg mélange plusieurs couches techniques : logique JavaScript côté éditeur, rendu PHP côté serveur pour les blocs dynamiques, et markup HTML validé à chaque ouverture de l’éditeur. Chacune de ces couches peut casser indépendamment des autres, souvent de façon silencieuse, révélée seulement quand un utilisateur tombe sur un bloc invalide plusieurs mois après la mise en production d’un changement anodin.
WordPress fournit un ensemble d’outils de test officiels, pensés spécifiquement pour les blocs. Cet article présente les deux niveaux de tests les plus utiles au quotidien : les tests unitaires avec Jest, et les tests de bout en bout qui simulent un vrai parcours utilisateur dans l’éditeur.
Les tests unitaires avec @wordpress/jest-preset-default
Le paquet @wordpress/jest-preset-default configure Jest avec les transformations nécessaires pour tester du code React et JSX écrit avec @wordpress/element, sans configuration manuelle. Il s’installe et se déclare simplement dans le package.json :
{
"scripts": {
"test-unit": "wp-scripts test-unit-js"
},
"devDependencies": {
"@wordpress/scripts": "^26.0.0"
}
}
La commande wp-scripts test-unit-js, fournie par @wordpress/scripts, embarque déjà ce preset. Un test unitaire typique vérifie qu’une fonction de transformation d’attributs, ou une fonction save(), produit bien le résultat attendu :
import { render, screen } from '@testing-library/react';
import Edit from '../src/edit';
test( 'affiche le message par défaut si aucun texte n\'est saisi', () => {
render(
<Edit attributes={ { message: '' } } setAttributes={ () => {} } />
);
expect( screen.getByText( /Saisissez votre message/i ) ).toBeInTheDocument();
} );
Ces tests unitaires sont rapides à exécuter (quelques secondes pour l’ensemble d’une suite), ce qui les rend adaptés à une exécution systématique avant chaque commit, ou dans une étape d’intégration continue.
Tester la fonction save() et la validation du markup

Un test particulièrement utile, souvent négligé, consiste à vérifier que le HTML produit par save() reste stable pour un jeu d’attributs donné. C’est exactement le type de régression qui déclenche une erreur de validation de bloc en production : un changement anodin dans save() qui modifie le markup sans déprécation associée.
import { serialize, createBlock } from '@wordpress/blocks';
test( 'le markup du bloc reste stable', () => {
const block = createBlock( 'wpmoderne/bandeau-alerte', {
message: 'Attention aux mises à jour',
niveau: 'alerte',
} );
expect( serialize( block ) ).toMatchSnapshot();
} );
Ce test par « snapshot » échoue automatiquement dès que le markup change, forçant le développeur à se poser la question : ce changement nécessite-t-il une entrée dans le tableau deprecated du bloc ? C’est un excellent filet de sécurité, particulièrement sur un projet avec plusieurs contributeurs.
Les tests de bout en bout avec e2e-test-utils-playwright
Les tests unitaires ne couvrent pas tout : ils ne vérifient pas qu’un bloc s’insère correctement depuis l’inserteur, que les contrôles de l’inspecteur répondent bien au clic, ou que le contenu se sauvegarde sans erreur. Pour cela, WordPress fournit @wordpress/e2e-test-utils-playwright, une surcouche de Playwright avec des utilitaires prêts à l’emploi pour piloter l’éditeur.
import { test, expect } from '@wordpress/e2e-test-utils-playwright';
test( 'insère le bloc bandeau d\'alerte et modifie son niveau', async ( { editor, page } ) => {
await editor.insertBlock( { name: 'wpmoderne/bandeau-alerte' } );
await page.getByLabel( 'Niveau' ).selectOption( 'erreur' );
const content = await editor.getEditedPostContent();
expect( content ).toContain( 'wp:wpmoderne/bandeau-alerte' );
expect( content ).toContain( '"niveau":"erreur"' );
} );
Ce type de test démarre un vrai navigateur, ouvre une véritable instance de WordPress (généralement via un environnement @wordpress/env local), et interagit avec l’interface exactement comme le ferait un utilisateur. Plus lent qu’un test unitaire, mais bien plus proche de la réalité d’usage.
Organiser sa stratégie de tests
Sur un projet de taille moyenne avec une dizaine de blocs personnalisés, une répartition efficace consiste à réserver les tests unitaires aux fonctions pures (transformation d’attributs, validation, calculs) et aux snapshots de save(), puis à limiter les tests de bout en bout aux parcours les plus critiques : insertion du bloc, remplissage des champs principaux, et vérification que le contenu sauvegardé est valide.
- Un snapshot de
save()par bloc, pour détecter tout changement de markup non intentionnel. - Des tests unitaires ciblés sur la logique métier de
edit()(affichage conditionnel, calculs d’attributs dérivés). - Un test de bout en bout par bloc pour le parcours d’insertion de base, et davantage pour les blocs avec une logique d’édition complexe.
Un snapshot qui casse n’est jamais une mauvaise nouvelle en soi : c’est une question posée par le test. La vraie erreur, c’est de répondre « oui, tout va bien » sans avoir vérifié qu’une déprécation n’était pas nécessaire.
Intégrer les tests à la chaîne de publication
Sur nos projets, les tests unitaires s’exécutent à chaque push via une action d’intégration continue, en quelques secondes. Les tests de bout en bout, plus longs, tournent plutôt sur les pull requests visant la branche principale, avec un environnement @wordpress/env démarré dans un conteneur dédié. Cette séparation évite de ralentir le cycle de développement quotidien tout en gardant un filet de sécurité solide avant chaque mise en production.
En résumé
Tester ses blocs Gutenberg ne demande pas d’outillage exotique : WordPress fournit tout le nécessaire avec @wordpress/jest-preset-default pour l’unitaire et @wordpress/e2e-test-utils-playwright pour le bout en bout. Le réflexe le plus rentable reste le snapshot de save() : peu coûteux à écrire, il attrape justement les régressions les plus sournoises, celles qui ne se révèlent qu’en production, longtemps après le déploiement.