claude plugin test. Un test lève les événements que vos hooks gèrent et vérifie ce que les hooks ont fait, afin que vous détectiez un problème avant qu’il n’atteigne une session. Le premier exemple teste le mod de Créer un mod.
Écrire un test
Un test charge votre mod, envoie des événements via ses hooks de la manière que Claude Code le ferait, et vérifie ce que les hooks ont fait, sans session, connexion ou réseau. Vous exécutez les tests depuis votre shell avecclaude plugin test, et chaque fichier de test importe le kit de test, une bibliothèque de test dans le module claude-code/testing.
Donnez à chaque fichier de test un nom qui se termine par .test.ts, comme first-mod.test.ts, et enregistrez-le n’importe où dans le répertoire du plugin. Chaque fichier de test a besoin d’au moins un test(), sinon l’exécution échoue avec declares no test(): nothing ran. Un fichier de test peut importer vos propres fichiers du mod et les helpers .ts frères, afin que vous puissiez tester les fonctions simples, comme les règles d’un jeu, sans le kit.
Ce test lève deux appels d’outils, exécute la commande /tally de Créer un mod, et vérifie que la réponse compte les deux. Sa première ligne est un stub, qui répond aux appels d’outils à la place de Claude Code. Enregistrez-le sous first-mod/tests/first-mod.test.ts :
first-mod/tests/first-mod.test.ts
first-mod :
$.tool.call a traversé le hook tool.call du mod, qui a ajouté un à son compte et a transmis l’appel au stub. Aucun ls n’a été exécuté et aucun fichier n’a été lu. $.command.run a ensuite accédé au hook command.run du mod, et answer est l’objet que ce hook a retourné.
La commande se termine avec le statut 1 quand un test échoue, afin qu’elle fonctionne dans CI. Si vos propres mods ne peuvent pas se charger dans le shell qui l’exécute, il imprime une ligne commençant par claude plugin test: hooks modules are turned off avec la raison, et se termine avec le statut 1.
Remplacer ce que Claude Code répondrait
Aucun modèle, magasin ou outil ne s’exécute dans un test, donc partout où votre mod s’attend à ce que Claude Code réponde, le test fournit la réponse avec un stub. Une fonction de test reçoit deux arguments pour cela :$: le propre$du test, qui se tient à la place de Claude Code. Ce n’est pas l’API des mods qu’un hook reçoit. Chacune de ses méthodes lève l’événement du même nom, l’envoie via les hooks de votre mod, et se résout au résultat :$.tool.call({ tool: 'Bash', command: 'ls' })lèvetool.call.$.command.run,$.prompt.submit,$.session.start, et$.turn.completefonctionnent de la même manière, et$.classic.Stopet les autres méthodes$.classiclèvent un événement de hook de paramètres. Un test ne peut pas lever directement un appel d’API des mods commeui.close. Déclenchez-le via votre mod, par exemple en appuyant sur le bouton qui ferme le volet.on: appelez-le pour enregistrer des stubs, qui sont des hooks qui répondent à la place de Claude Code. Nommez un stub pour un appel d’API des mods sans le$., afin qu’un stub enregistré commestore.getréponde à votre mod$.store.get. Quand votre mod appelle$.model.completeou$.store.get, un stub fournit la réponse.
grader, et gère une commande /grade qui envoie une phrase à un modèle et rapporte si la réponse commence par PASS. Le fichier ne contient que le hook testé, donc le mod a également besoin d’un plugin.json et d’un hooks.json, comme dans Créer un mod. Pour taper /grade dans une session, le mod doit également enregistrer la commande :
grader/hooks/register.js
grader/tests/grader.test.ts
reply du hook est l’objet sous value, dont le text commence par PASS. Pour vérifier l’autre branche, ajoutez un deuxième test dont le stub retourne un text qui commence par FAIL, et attendez Try again.
Un stub pour un appel d’API des mods retourne un objet avec un champ value, qui contient ce que l’appel se résout en dans votre mod : { value: 7 } fait que $.store.get se résout à 7. Un stub pour l’un des événements de Claude Code, comme turn.step ou tool.call, retourne le propre résultat de cet événement, comme { result: 'ok' }. $.session.send et $.prompt.fill prennent également le résultat de l’événement, comme le montre le tableau. Rechercher ce qu’un stub retourne montre quelle forme chaque nom courant prend. Deux erreurs signifient qu’un stub est incorrect ou manquant. La sortie d’un test échoué inclut un bloc intitulé the engine reported:, et chaque erreur y apparaît :
returned neither { value } nor { deny }: un stub pour un appel d’API des mods a retourné une valeur nueno implementation forsuivi d’un nom : votre mod a fait cet appel et aucun stub ne le répond
mock.clock(on) répond à $.clock, mock.store(on, { count: 7 }) répond à $.store à partir d’un magasin qui commence par ces entrées, et mock.env(on, { CI: 'true' }) répond à $.env.get à partir de ces variables. mock.clock retourne une horloge simulée que votre test avance, afin qu’un test d’une minuterie n’attende pas. mock.store ne retourne rien, donc pour vérifier ce que votre mod a enregistré, écrivez vous-même les deux stubs store comme le fait le test de dessin.
Suivre les règles du kit de test
Le kit de test a quelques règles qui lui sont propres, et en enfreindre une produit les erreurs que les nouveaux auteurs de tests rencontrent en premier :-
Enregistrez chaque stub avant le premier appel du test sur
$. Appeleronaprès cela lève une erreur commeon("ui.render") after the test first called $. -
session.startne s’exécute pas par lui-même. Chaque test commence avec votre module fraîchement chargé et aucun de ses hooks appelés, donc les variables au niveau du module conservent leurs valeurs initiales. Si un hook dépend de ce quesession.startconfigure, levez-le d’abord :Le deuxième stub répond à l’appel$.command.registerqu’un hooksession.startcomme celui du tutoriel fait. Sans lui, cet appel rejette avecno implementation for command.registeret le kit saute votre hook, donc rien après l’appel dans le hook ne s’exécute. Le test n’échoue pas à ce stade. Le hook ignoré est listé sousthe engine reported:uniquement si une vérification ultérieure échoue. -
Un hook qui retourne
next(e)a besoin d’un stub pour répondre. Quand votre hookui.renderretournenext(e), par exemple pour ne rien dessiner pendant que Claude est inactif, le monter échoue avecno implementation for ui.render. Enregistrez un stub qui retourne un élément en tant que données simples :Avec le stub enregistré, le montage réussit, etui.find({ type: 'Text' })retourne cet élément chaque fois que votre hook a retournénext(e). -
Un stub pour
turn.stepest un générateur asynchrone, et le test lit le flux jusqu’à la fin pour obtenir le résultat :Quand la boucle se termine,resultest l’objet que le stub a retourné, après que votre hookturn.stepait eu la chance de le modifier. Iciresult.answerest'ok'. -
Levez un appel d’outil avec le nom et les arguments de l’outil en tant que champs, comme
await $.tool.call({ tool: 'Bash', command: 'ls' }), et enregistrez un stubtool.callqui retourne{ result }.
Rechercher ce qu’un stub retourne
Chaque appel d’API des mods que votre mod fait dans un test a besoin d’un stub qui répond à la place de Claude Code, sauf les quelques-uns que le kit répond lui-même : les appels$.ui.invalidate et $.state. Pour les appels $.clock, utilisez mock.clock(on), sinon votre mod $.clock.now() échoue avec no implementation for clock.now.
Ce tableau liste ceux que les mods utilisent le plus. La première colonne est l’appel que votre mod fait ou l’événement qu’il transmet avec next(e). La deuxième est la fonction à passer à on sous ce nom, afin que la ligne $.store.get devienne on('store.get', ($, e) => ({ value: saved.get(e.key) })). Un '...' dans un stub marque le texte pour vous à remplir :
expect a les assertions toBe, toEqual, toMatch, toMatchObject, toContain, toBeDefined, toBeUndefined, et toThrow, et .not avant n’importe lequel d’entre eux.
Tester une minuterie
Un mod qui exécute du travail sur une minuterie a besoin d’une horloge que le test contrôle, afin que le test puisse avancer le temps au lieu d’attendre.const clock = mock.clock(on) retourne une horloge simulée qui commence à 0 et ne se déplace que quand votre test la déplace. Pour commencer à un autre moment, passez-le en millisecondes, comme dans mock.clock(on, { now: 5000 }). L’horloge a ces méthodes :
Ce hook appartient à un mod nommé
countdown, et gère une commande /countdown qui prend un nombre de secondes, démarre une minuterie $.clock.every d’une seconde, et affiche un toast à zéro. Comme avec grader, le fichier ne contient que le hook testé et n’enregistre pas la commande :
countdown/hooks/register.js
/countdown 3 et déplace l’horloge simulée, afin qu’il vérifie trois secondes de comportement sans attendre trois secondes :
countdown/tests/countdown.test.ts
expect montre que le toast ne vient pas tôt, et le deuxième montre qu’il vient une fois. Chaque advance se résout après que les minuteries qui sont venues à échéance aient exécuté, afin que la vérification sur la ligne suivante voie leur effet.
Tester un dessin
Un test peut dessiner l’un de vos sites de rendu du mod, puis appuyer, taper et trouver les éléments qu’il a dessinés.$.ui.mount dessine le site via le hook ui.render de votre mod et retourne un handle avec une méthode pour chacun d’eux. Pour couvrir plusieurs applications dans un test, définissez surface sur l’application à dessiner. Ce test ouvre le volet de Construire un volet avec des onglets, bascule les onglets, appuie sur le bouton, et vérifie le compte dans le terminal et l’application Desktop :
hello-tabs/tests/hello-tabs.test.ts
claude plugin test depuis le répertoire hello-tabs. Le test réussit quand les deux applications dessinent la ligne de compte et le mod a enregistré 2. Le compte se reporte de la première application à la deuxième parce que les deux montages utilisent le même module chargé.
Le handle que $.ui.mount retourne a ces méthodes, qui adressent les éléments par la key que vous leur avez donnée :
Chaque méthode se résout après que votre gestionnaire ait terminé, afin que vous puissiez vérifier le résultat sur la ligne suivante. Définissez
props à ce que Claude Code passerait pour ce site. Le tableau des sites de rendu liste les props de chaque site, et les types pour votre build ont leurs types.
Un test de dessin vérifie l’arborescence que votre hook retourne et si elle est valide pour cette application. Il ne vérifie pas comment l’application la peint, donc regardez une nouvelle mise en page dans une vraie session aussi.
Tester un dessin après /clear
Chaque test commence avec chaque valeur $.state à sa valeur par défaut, ce qui est comment /clear les laisse. Pour tester ce que votre mod fait ensuite, ignorez session.start, levez classic.SessionStart avec source: 'clear', et vérifiez ce que votre mod dessine.
Ce test vérifie le module de Charger une valeur enregistrée à nouveau après /clear. Ajoutez-le au fichier de Tester un dessin, où PANE est défini. Le premier test de ce fichier s’attend à ce que le bouton enregistre le compte, comme le bouton dans Enregistrer à partir de plus d’une session le fait :
hello-tabs/tests/hello-tabs.test.ts
classic.SessionStart a copié le 7 enregistré dans $.state avant que le volet ne se dessine. Sans ce hook dans votre module, le volet dessine Count: 0, find retourne undefined, et le test échoue à toBeDefined.
Tester un mod qui juge d’autres mods
Un mod que votre organisation liste dansprependPlugins peut refuser un autre mod avant qu’il ne se charge. Pour en tester un, définissez le tier de votre mod et donnez au test un deuxième mod pour que le vôtre admette ou refuse :
tier: appelez-le une fois en haut du fichier de test, comme danstier('prepend'), pour charger votre mod commeprepend,append, oubuiltin, sa place dans l’ordre dans lequel les mods s’exécutent. Sans lui, votre mod se charge commeuser.plugins: passez àtestun objet d’options avant le corps du test. Son tableaupluginscontient des mods que vous écrivez en ligne, chacun avec unnameet une fonctionregister. Pour charger un ailleurs queuser, ajouteztierà celui-ci.
acme-guard/tests/guard.test.ts
claude plugin test depuis le répertoire acme-guard. Les deux tests réussissent avec le mod de politique comme la page d’administration le montre.
Le kit charge chaque mod au premier appel du test sur $. Quand votre mod en refuse un, cet appel lance une exception, et le message nomme le mod refusé, le mod qui l’a refusé, et votre raison. Dans le deuxième test rien n’est refusé, donc reader répond à l’appel d’outil avant qu’il n’atteigne le stub.
Étapes suivantes
- Dépanner un mod : découvrez pourquoi un mod ne fait rien dans une session
- Référence des mods : chaque événement d’entrée et résultat, pour écrire des stubs