Skip to main content
Puedes escribir pruebas automatizadas para un mod y ejecutarlas desde tu shell con claude plugin test. Una prueba genera los eventos que tus hooks manejan y verifica qué hicieron los hooks, para que detectes un problema antes de que llegue a una sesión. El primer ejemplo prueba el mod de Crear un mod.

Escribe una prueba

Una prueba carga tu mod, envía eventos a través de sus hooks de la manera que lo haría Claude Code, y verifica qué hicieron los hooks, sin una sesión, un inicio de sesión ni una red. Ejecutas las pruebas desde tu shell con claude plugin test, y cada archivo de prueba importa el kit de pruebas, una biblioteca de pruebas en el módulo claude-code/testing. Dale a cada archivo de prueba un nombre que termine en .test.ts, como first-mod.test.ts, y guárdalo en cualquier lugar del directorio del plugin. Cada archivo de prueba necesita al menos una test(), o la ejecución falla con declares no test(): nothing ran. Un archivo de prueba puede importar los propios archivos de tu mod y helpers .ts hermanos, para que puedas hacer pruebas unitarias de funciones simples, como las reglas de un juego, sin el kit. Esta prueba genera dos llamadas de herramientas, ejecuta el comando /tally de Crear un mod, y verifica que la respuesta cuente ambas. Su primera línea es un stub, que responde las llamadas de herramientas en lugar de Claude Code. Guárdalo como first-mod/tests/first-mod.test.ts:
first-mod/tests/first-mod.test.ts
En tu shell, ejecuta las pruebas desde el directorio first-mod:
La salida nombra cada prueba y si pasó, con tiempos que varían de una ejecución a otra:
Cada $.tool.call pasó a través del hook tool.call del mod, que agregó uno a su contador y pasó la llamada al stub. No se ejecutó ls y no se leyó ningún archivo. $.command.run luego fue al hook command.run del mod, y answer es el objeto que ese hook devolvió. El comando sale con estado 1 cuando una prueba falla, por lo que funciona en CI. Si tus propios mods no pueden cargarse en el shell que lo ejecuta, imprime una línea que comienza con claude plugin test: hooks modules are turned off con la razón, y sale con estado 1.

Simula lo que Claude Code respondería

Ningún modelo, almacén o herramienta se ejecuta en una prueba, por lo que dondequiera que tu mod espere que Claude Code responda, la prueba proporciona la respuesta con un stub. Una función de prueba recibe dos argumentos para eso:
  • $: el $ propio de la prueba, que se sitúa donde Claude Code lo hace. No es la API de mods que recibe un hook. Cada uno de sus métodos genera el evento del mismo nombre, lo envía a través de los hooks de tu mod, y se resuelve al resultado: $.tool.call({ tool: 'Bash', command: 'ls' }) genera tool.call. $.command.run, $.prompt.submit, $.session.start, y $.turn.complete funcionan de la misma manera, y $.classic.Stop y los otros métodos $.classic generan un evento de hook de configuración. Una prueba no puede generar directamente una llamada de API de mods como ui.close. Actívala a través de tu mod, por ejemplo presionando el botón que cierra el panel.
  • on: llámalo para registrar stubs, que son hooks que responden en lugar de Claude Code. Nombra un stub para una llamada de API de mods sin el $., por lo que un stub registrado como store.get responde a $.store.get de tu mod. Cuando tu mod llama a $.model.complete o $.store.get, un stub proporciona la respuesta.
Este ejemplo simula una llamada de modelo. El hook pertenece a un mod llamado grader, y maneja un comando /grade que envía una oración a un modelo e informa si la respuesta comienza con PASS. El archivo contiene solo el hook bajo prueba, por lo que el mod también necesita un plugin.json y un hooks.json, como en Crear un mod. Para escribir /grade en una sesión, el mod también tiene que registrar el comando:
grader/hooks/register.js
Esta prueba simula la llamada del modelo para verificar qué hace el hook con una respuesta aprobada:
grader/tests/grader.test.ts
La prueba pasa porque el reply del hook es el objeto bajo value, cuyo text comienza con PASS. Para verificar la otra rama, agrega una segunda prueba cuyo stub devuelva un text que comience con FAIL, y espera Try again. Un stub para una llamada de API de mods devuelve un objeto con un campo value, que contiene lo que la llamada se resuelve en tu mod: { value: 7 } hace que $.store.get se resuelva a 7. Un stub para uno de los eventos de Claude Code, como turn.step o tool.call, devuelve el resultado propio de ese evento, como { result: 'ok' }. $.session.send y $.prompt.fill también toman el resultado del evento, como muestra la tabla. Busca qué devuelve un stub muestra qué forma toma cada nombre común. Dos errores significan que un stub es incorrecto o falta. La salida de una prueba fallida incluye un bloque encabezado the engine reported:, y cada error aparece allí:
  • returned neither { value } nor { deny }: un stub para una llamada de API de mods devolvió un valor simple
  • no implementation for seguido de un nombre: tu mod hizo esa llamada y ningún stub la responde
El kit también exporta mocks en memoria que responden un espacio de nombres completo por ti. mock.clock(on) responde $.clock, mock.store(on, { count: 7 }) responde $.store desde un almacén que comienza con esas entradas, y mock.env(on, { CI: 'true' }) responde $.env.get desde esas variables. mock.clock devuelve un reloj simulado que tu prueba avanza, por lo que una prueba de un temporizador no espera. mock.store no devuelve nada, por lo que para verificar qué guardó tu mod, escribe los dos stubs store tú mismo como lo hace la prueba de dibujo.

Sigue las reglas del kit de pruebas

El kit de pruebas tiene algunas reglas propias, y romper una produce los errores que los nuevos autores de pruebas encuentran primero:
  • Registra cada stub antes de la primera llamada de la prueba a $. Llamar a on después de eso lanza un error como on("ui.render") after the test first called $.
  • session.start no se ejecuta por sí solo. Cada prueba comienza con tu módulo recién cargado y ninguno de sus hooks llamado, por lo que las variables a nivel de módulo mantienen sus valores iniciales. Si un hook depende de lo que session.start configura, genéralo primero:
    El segundo stub responde la llamada $.command.register que hace un hook session.start como el del tutorial. Sin él, esa llamada rechaza con no implementation for command.register y el kit omite tu hook, por lo que nada después de la llamada en el hook se ejecuta. La prueba no falla en ese punto. El hook omitido se enumera bajo the engine reported: solo si una verificación posterior falla.
  • Un hook que devuelve next(e) necesita un stub para responder. Cuando tu hook ui.render devuelve next(e), por ejemplo para no dibujar nada mientras Claude está inactivo, montarlo falla con no implementation for ui.render. Registra un stub que devuelva un elemento como datos simples:
    Con el stub registrado, el montaje tiene éxito, y ui.find({ type: 'Text' }) devuelve ese elemento siempre que tu hook devolvió next(e).
  • Un stub para turn.step es un generador asincrónico, y la prueba lee el flujo hasta su fin para obtener el resultado:
    Cuando el bucle termina, result es el objeto que el stub devolvió, después de que tu hook turn.step haya tenido la oportunidad de cambiarlo. Aquí result.answer es 'ok'.
  • Genera una llamada de herramienta con el nombre de la herramienta y los argumentos como campos, como await $.tool.call({ tool: 'Bash', command: 'ls' }), y registra un stub tool.call que devuelva { result }.

Busca qué devuelve un stub

Cada llamada de API de mods que tu mod hace en una prueba necesita un stub que responda en lugar de Claude Code, excepto las pocas que el kit responde por sí solo: llamadas $.ui.invalidate y $.state. Para llamadas $.clock, usa mock.clock(on), o $.clock.now() de tu mod falla con no implementation for clock.now. Esta tabla enumera las que los mods usan más. La primera columna es la llamada que tu mod hace o el evento que pasa con next(e). La segunda es la función a pasar a on bajo ese nombre, por lo que la fila $.store.get se convierte en on('store.get', ($, e) => ({ value: saved.get(e.key) })). Un '...' en un stub marca texto para que lo completes: expect tiene las aserciones toBe, toEqual, toMatch, toMatchObject, toContain, toBeDefined, toBeUndefined, y toThrow, y .not antes de cualquiera de ellas.

Prueba un temporizador

Un mod que ejecuta trabajo en un temporizador necesita un reloj que la prueba controle, para que la prueba pueda avanzar el tiempo en lugar de esperar. const clock = mock.clock(on) devuelve un reloj simulado que comienza en 0 y se mueve solo cuando tu prueba lo mueve. Para comenzar en otro momento, pásalo en milisegundos, como en mock.clock(on, { now: 5000 }). El reloj tiene estos métodos: Este hook pertenece a un mod llamado countdown, y maneja un comando /countdown que toma un número de segundos, inicia un temporizador $.clock.every de un segundo, y muestra un toast en cero. Como con grader, el archivo contiene solo el hook bajo prueba y no registra el comando:
countdown/hooks/register.js
Esta prueba ejecuta /countdown 3 y mueve el reloj simulado, por lo que verifica tres segundos de comportamiento sin esperar tres segundos:
countdown/tests/countdown.test.ts
El primer expect muestra que el toast no viene temprano, y el segundo muestra que viene una vez. Cada advance se resuelve después de que los temporizadores que vencieron hayan ejecutado, por lo que la verificación en la siguiente línea ve su efecto.

Prueba un dibujo

Una prueba puede dibujar uno de los sitios de renderizado de tu mod, luego presionar, escribir en, y encontrar los elementos que dibujó. $.ui.mount dibuja el sitio a través del hook ui.render de tu mod y devuelve un identificador con un método para cada uno de esos. Para cubrir varias aplicaciones en una prueba, establece surface en la aplicación para la que dibujar. Esta prueba abre el panel de Construye un panel con pestañas, cambia pestañas, presiona el botón, y verifica el contador en la terminal y la aplicación de escritorio:
hello-tabs/tests/hello-tabs.test.ts
En tu shell, ejecuta claude plugin test desde el directorio hello-tabs. La prueba pasa cuando ambas aplicaciones dibujan la línea de contador y el mod ha guardado 2. El contador se transfiere de la primera aplicación a la segunda porque ambos montajes usan el mismo módulo cargado. El identificador que $.ui.mount devuelve tiene estos métodos, que direccionan elementos por la key que les diste: Cada método se resuelve después de que tu controlador haya terminado, por lo que puedes verificar el resultado en la siguiente línea. Establece props en lo que Claude Code pasaría para ese sitio. La tabla de sitios de renderizado enumera los props de cada sitio, y los tipos para tu compilación tienen sus tipos. Una prueba de dibujo verifica el árbol que devuelve tu hook y si es válido para esa aplicación. No verifica cómo la aplicación lo pinta, así que mira un nuevo diseño en una sesión real también.

Prueba un dibujo después de /clear

Cada prueba comienza con cada valor $.state en su predeterminado, que es cómo /clear los deja. Para probar qué hace tu mod a continuación, omite session.start, genera classic.SessionStart con source: 'clear', y verifica qué dibuja tu mod. Esta prueba verifica el módulo de Carga un valor guardado nuevamente después de /clear. Agrégalo al archivo de Prueba un dibujo, donde PANE está definido. La primera prueba de ese archivo espera que el botón guarde el contador, como lo hace el botón en Guarda desde más de una sesión:
hello-tabs/tests/hello-tabs.test.ts
La prueba pasa cuando tu hook classic.SessionStart ha copiado el 7 guardado en $.state antes de que el panel se dibuje. Sin ese hook en tu módulo, el panel dibuja Count: 0, find devuelve undefined, y la prueba falla en toBeDefined.

Prueba un mod que juzga otros mods

Un mod que tu organización enumera en prependPlugins puede rechazar otro mod antes de que cargue. Para probar uno, establece el nivel de tu mod y dale a la prueba un segundo mod para que el tuyo admita o rechace:
  • tier: llámalo una vez en la parte superior del archivo de prueba, como en tier('prepend'), para cargar tu mod como prepend, append, o builtin, su lugar en el orden en que los mods se ejecutan. Sin él, tu mod carga como user.
  • plugins: pasa a test un objeto de opciones antes del cuerpo de la prueba. Su matriz plugins contiene mods que escribes en línea, cada uno con un name y una función register. Para cargar uno en algún lugar que no sea user, agrega tier a él.
Este archivo de prueba carga el mod de política de la página de administración primero. Verifica que el mod de política rechace un mod que inicia un proceso y admita uno que no:
acme-guard/tests/guard.test.ts
En tu shell, ejecuta claude plugin test desde el directorio acme-guard. Ambas pruebas pasan con el mod de política como lo muestra la página de administración. El kit carga cada mod en la primera llamada de la prueba a $. Cuando tu mod rechaza uno, esa llamada lanza, y el mensaje nombra el mod rechazado, el mod que lo rechazó, y tu razón. En la segunda prueba nada es rechazado, por lo que reader responde la llamada de herramienta antes de que llegue al stub.

Próximos pasos