> ## Documentation Index
> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Prueba un mod

> Escribe pruebas automatizadas para un mod de Claude Code que generen eventos, simulen las respuestas de Claude Code y presionen botones, sin sesión, inicio de sesión ni red.

Puedes escribir pruebas automatizadas para un mod y ejecutarlas desde tu shell con [`claude plugin test`](/docs/es/plugins/mods/reference#commands). 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](/docs/es/plugins/mods/create).

<h2 id="write-a-test">
  Escribe una prueba
</h2>

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](/docs/es/plugins/mods/create), y verifica que la respuesta cuente ambas. Su primera línea es un [stub](#stub-what-claude-code-would-answer), que responde las llamadas de herramientas en lugar de Claude Code. Guárdalo como `first-mod/tests/first-mod.test.ts`:

```typescript first-mod/tests/first-mod.test.ts theme={null}
import { expect, test } from 'claude-code/testing'

test('/tally reports the tool calls the mod has seen', async ($, on) => {
  // Answer each tool call in Claude Code's place, so no tool runs
  on('tool.call', () => ({ result: 'ok' }))

  // Raise two tool calls, which the mod's tool.call hook counts
  await $.tool.call({ tool: 'Bash', command: 'ls' })
  await $.tool.call({ tool: 'Read', file_path: 'README.md' })

  // Run /tally and check the text its hook returns
  const answer = await $.command.run({ command: 'tally', args: '' })
  expect(answer.text).toBe('Claude has made 2 tool calls since this mod loaded')
})
```

En tu shell, ejecuta las pruebas desde el directorio `first-mod`:

```bash theme={null}
claude plugin test
```

La salida nombra cada prueba y si pasó, con tiempos que varían de una ejecución a otra:

```text theme={null}
tests/first-mod.test.ts:
(pass) /tally reports the tool calls the mod has seen [22.87ms]

 1 pass
 0 fail
Ran 1 test across 1 file. [0.19s]
```

Cada `$.tool.call` pasó a través del hook [`tool.call`](/docs/es/plugins/mods/reference#tools) 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`](/docs/es/plugins/mods/reference#commands-and-configuration) 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.

<h3 id="stub-what-claude-code-would-answer">
  Simula lo que Claude Code respondería
</h3>

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](/docs/es/plugins/mods/reference#mods-api-methods) 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](/docs/es/plugins/mods/events#hook-the-settings-hook-events). 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`](/docs/es/plugins/mods/api#call-a-model) o [`$.store.get`](/docs/es/plugins/mods/interface#keep-state), 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](/docs/es/plugins/mods/create#write-a-mod-yourself). Para escribir `/grade` en una sesión, el mod también tiene que [registrar el comando](/docs/es/plugins/mods/api#add-a-command):

```javascript grader/hooks/register.js theme={null}
export function register(on) {
  on('command.run', { command: 'grade' }, async ($, e) => {
    // e.args is the text typed after /grade
    const reply = await $.model.complete({
      model: 'haiku',
      system: 'Grade the sentence. Start your reply with PASS or FAIL.',
      prompt: e.args,
    })
    const passed = reply.isAnswered && reply.text.startsWith('PASS')
    return { text: passed ? 'Passed' : 'Try again' }
  })
}
```

Esta prueba simula la llamada del modelo para verificar qué hace el hook con una respuesta aprobada:

```typescript grader/tests/grader.test.ts theme={null}
import { expect, test } from 'claude-code/testing'

test('a passing grade is reported', async ($, on) => {
  // Answer the mod's $.model.complete call with a fixed reply, so no model runs
  on('model.complete', () => ({
    value: {
      isAnswered: true,
      text: 'PASS\nNice sentence.',
      usage: { input_tokens: 10, output_tokens: 5, cache_read_input_tokens: 0, cache_creation_input_tokens: 0 },
    },
  }))

  // Run /grade, which makes the mod call the model
  const answer = await $.command.run({ command: 'grade', args: 'The cat sat on the mat.' })
  expect(answer.text).toBe('Passed')
})
```

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`](/docs/es/plugins/mods/reference#turns) 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](#look-up-what-a-stub-returns) 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`](/docs/es/plugins/mods/api#run-work-in-the-background), `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](#test-a-drawing).

<h3 id="follow-the-test-kit’s-rules">
  Sigue las reglas del kit de pruebas
</h3>

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`](/docs/es/plugins/mods/reference#session) 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:

  ```typescript theme={null}
  // Answer the event after your hook passes it on with next(e)
  on('session.start', () => ({ cwd: '/work' }))
  // Answer the $.command.register call your hook makes
  on('command.register', () => ({ value: undefined }))
  // Raise the event, which runs your session.start hook
  await $.session.start({ surface: 'terminal', isInteractive: true, cwd: '/work' })
  ```

  El segundo stub responde la llamada `$.command.register` que hace un hook `session.start` como el del [tutorial](/docs/es/plugins/mods/create#write-a-mod-yourself). 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`](/docs/es/plugins/mods/reference#interface) devuelve `next(e)`, por ejemplo para no dibujar nada mientras Claude está inactivo, [montarlo](#test-a-drawing) falla con `no implementation for ui.render`. Registra un stub que devuelva un elemento como datos simples:

  ```typescript theme={null}
  // Stands for what Claude Code would draw at the site
  on('ui.render', () => ({ type: 'Text', props: {}, children: ['drawn by Claude Code'] }))
  ```

  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:

  ```typescript theme={null}
  on('turn.step', async function* ($, e) {
    // Each yield is one piece of the model's streamed reply
    yield { kind: 'text', index: 0, text: 'ok' }
    // The return value is the result of the whole request
    return { turnId: e.turnId, index: e.index, answer: 'ok', toolUses: [], stopReason: 'end_turn', usage: null }
  })

  // Raise one request to the model, which runs your turn.step hook
  const stream = $.turn.step({ turnId: 't', index: 0, model: 'claude-test', messageCount: 1 })
  // Read every piece until the stream says it's done
  let step = await stream.next()
  while (step.done !== true) step = await stream.next()
  const result = step.value
  ```

  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 }`.

<h3 id="look-up-what-a-stub-returns">
  Busca qué devuelve un stub
</h3>

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`](/docs/es/plugins/mods/interface#redraw-when-something-changes) y [`$.state`](/docs/es/plugins/mods/interface#keep-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:

| Tu mod llama o pasa | Stub |
| :- | :- |
| `$.command.register`, `$.tool.register`, `$.ui.toast`, `$.ui.log`, `$.ui.status`, `$.ui.close`, `$.store.set` | `() => ({ value: undefined })`. Para `ui.toast` y `ui.log`, el texto es `e.text`. |
| `$.store.get` | `($, e) => ({ value: saved.get(e.key) })` |
| `$.fs.read` | `($, e) => ({ value: e.path.endsWith('notes.md') ? '# Notes' : '' })`. `e.path` llega como una ruta absoluta, así que compara con `endsWith`. |
| `$.ui.open` | `() => ({ value: { isPlaced: true } })` |
| `$.ui.ask` | Un stub `tool.call`, porque la pregunta la alcanza como una llamada a la herramienta `AskUserQuestion`: `($, e) => ({ result: { answers: { [e.questions[0].question]: 'Run it' } } })`. Verifica `e.tool` primero si tu mod pasa otras llamadas de herramientas. |
| `$.model.complete` | `() => ({ value: { isAnswered: true, text: '...', usage } })` |
| `$.process.run` | `($, e) => ({ value: { exitCode: 0, stdout: '...', stderr: '' } })`. `e.argv` es la lista de argumentos y `e.init` contiene `cwd` y `timeoutMs`. |
| Cualquier llamada de API de mods que debería fallar | `() => ({ deny: 'the reason' })`, que hace que la llamada rechace en tu mod. Un stub que lanza se omite en su lugar. |
| `session.start` | `() => ({ cwd: '/work' })` |
| `turn.start` | `($, e) => ({ turnId: e.turnId })` |
| `tool.call` | `() => ({ result: '...' })` |
| `turn.complete` | `() => ({ text: '' })`. Genéralo con `$.turn.complete({ turnId, answer, durationMs, isAborted: false, usage: null })`. |
| `prompt.submit` | `($, e) => ({ text: e.text })` |
| `prompt.fill` | `() => ({ isFilled: true })` |
| `$.prompt.read` | `() => ({ value: { text: '...', cursor: 0 } })` |
| `$.ui.copy` | `() => ({ value: { isCopied: true } })` |
| `$.session.messages` | `() => ({ value: [{ role: 'assistant', text: '...', toolUses: [] }] })` |
| `$.session.id`, `$.agent.list` | `() => ({ value: 'abc123' })`, `() => ({ value: [] })` |
| `session.send` | `() => ({ isDelivered: true })`. `e.to` llega como una cadena incluso cuando tu mod pasó `{ sessionId }`. |
| `session.receive` | `($, e) => ({ text: e.text })`. Genéralo con `$.session.receive({ origin: { kind: 'peer-send-message' }, text })`. |
| `ui.render` | `() => ({ type: 'Text', props: {}, children: ['...'] })` |

`expect` tiene las aserciones `toBe`, `toEqual`, `toMatch`, `toMatchObject`, `toContain`, `toBeDefined`, `toBeUndefined`, y `toThrow`, y `.not` antes de cualquiera de ellas.

<h2 id="test-a-timer">
  Prueba un temporizador
</h2>

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:

| Método | Lo que hace |
| :- | :- |
| `await clock.advance(1000)` | Avanza el tiempo por esa cantidad de milisegundos y ejecuta cada temporizador que vence |
| `await clock.set(5000)` | Avanza el tiempo a ese valor, como lo haría `advance` |
| `clock.now()` | Devuelve el tiempo, que es lo que `$.clock.now()` de tu mod se resuelve a |
| `await clock.settle()` | Ejecuta temporizadores que ya vencen, como una cadena de llamadas `$.clock.after` de cero retrasos, sin mover el tiempo |
| `await clock.sleep(2000)` | Dentro de un stub, hace que ese stub responda solo una vez que la prueba haya avanzado tan lejos, que es cómo simulas un modelo o proceso lento |

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:

```javascript countdown/hooks/register.js theme={null}
export function register(on) {
  on('command.run', { command: 'countdown' }, async ($, e) => {
    // e.args is the text typed after /countdown
    let left = Number(e.args)
    const timer = $.clock.every(1000, () => {
      left -= 1
      if (left === 0) {
        timer.cancel()
        $.ui.toast('Time is up')
      }
    })
    // Print nothing in the transcript
    return {}
  })
}
```

Esta prueba ejecuta `/countdown 3` y mueve el reloj simulado, por lo que verifica tres segundos de comportamiento sin esperar tres segundos:

```typescript countdown/tests/countdown.test.ts theme={null}
import { expect, mock, test } from 'claude-code/testing'

test('the countdown ends with a toast', async ($, on) => {
  // Answer every $.clock call from a clock the test controls
  const clock = mock.clock(on)
  // Collect the text of each toast the mod shows
  const toasts: string[] = []
  on('ui.toast', ($, e) => {
    toasts.push(e.text)
    return { value: undefined }
  })

  await $.command.run({ command: 'countdown', args: '3' })
  // After two seconds the timer has fired twice, and no toast is due
  await clock.advance(2000)
  expect(toasts).toEqual([])
  // The third second brings the count to zero
  await clock.advance(1000)
  expect(toasts).toEqual(['Time is up'])
})
```

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.

<h2 id="test-a-drawing">
  Prueba un dibujo
</h2>

Una prueba puede dibujar uno de los [sitios de renderizado](/docs/es/plugins/mods/reference#render-sites) 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](/docs/es/plugins/mods/interface#build-a-pane-with-tabs), cambia pestañas, presiona el botón, y verifica el contador en la terminal y la aplicación de escritorio:

```typescript hello-tabs/tests/hello-tabs.test.ts theme={null}
import { expect, test } from 'claude-code/testing'

// What Claude Code passes to a ui.render hook for this pane, apart from the app
const PANE = {
  plugin: 'hello-tabs',
  component: 'Pane',
  requestId: 'hello-tabs',
  viewport: { columns: 100, rows: 30 },
  props: {
    title: 'Hello tabs',
    isFocused: true,
    bodyColumns: 60,
    placement: 'inline',
    scroll: { offset: 0, bodyRows: 10 },
    view: {},
  },
} as const

test('the second tab counts presses and saves the count', async ($, on) => {
  // Stub $.store with a Map, so the test can read what the mod saved
  const saved = new Map<string, unknown>()
  on('store.get', ($, e) => ({ value: saved.get(e.key) }))
  on('store.set', ($, e) => {
    saved.set(e.key, e.value)
    return { value: undefined }
  })

  // Draw the pane once for each app
  for (const surface of ['terminal', 'desktop'] as const) {
    const ui = await $.ui.mount({ ...PANE, surface })
    // Press the buttons by the key the mod gave them
    await ui.press({ key: 'tab-two' })
    await ui.press({ key: 'more' })
    // The second tab's count line is in the drawing
    expect(await ui.find({ type: 'Text', text: /^Count: \d+$/ })).toBeDefined()
    await ui.unmount()
  }

  // One press in each app makes two
  expect(saved.get('count')).toBe(2)
})
```

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:

| Método | Lo que hace |
| :- | :- |
| `press({ key: 'more' })` | Presiona el `Button` con esa clave |
| `input({ key: 'new-note', text: 'buy milk' })` | Escribe el texto en el `Input` con esa clave y presiona Enter. Agrega `kind: 'change'` para escribir sin enviar. |
| `select({ key: 'size', value: 'large' })` | Elige la opción con ese valor en el `Select` con esa clave |
| `find({ key: 'more' })` o `find({ type: 'Text', text: 'Count: 2' })` | Devuelve el primer elemento coincidente como `{ type, props, children }`, o `undefined`. `text` puede ser una cadena o una expresión regular. |
| `unmount()` | Elimina el dibujo |

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](/docs/es/plugins/mods/reference#render-sites) enumera los props de cada sitio, y [los tipos para tu compilación](/docs/es/plugins/mods/create#get-the-types-for-your-build) 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.

<h3 id="test-a-drawing-after-clear">
  Prueba un dibujo después de `/clear`
</h3>

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`](/docs/es/plugins/mods/interface#load-a-saved-value-again-after-clear). Agrégalo al archivo de [Prueba un dibujo](#test-a-drawing), 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](/docs/es/plugins/mods/interface#save-from-more-than-one-session):

```typescript hello-tabs/tests/hello-tabs.test.ts theme={null}
test('the saved count comes back after /clear', async ($, on) => {
  // The store already holds a count of 7
  on('store.get', () => ({ value: 7 }))
  // Answer the event after your hook passes it on with next(e)
  on('classic.SessionStart', () => ({}))

  // Raise the event that fires after /clear, which runs your hook
  await $.classic.SessionStart({ source: 'clear' })

  const ui = await $.ui.mount({ ...PANE, surface: 'terminal' })
  await ui.press({ key: 'tab-two' })
  // The pane shows the stored count, not the default of 0
  expect(await ui.find({ type: 'Text', text: 'Count: 7' })).toBeDefined()
})
```

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`.

<h2 id="test-a-mod-that-judges-other-mods">
  Prueba un mod que juzga otros mods
</h2>

Un mod que tu organización enumera en [`prependPlugins`](/docs/es/plugins/mods/admin) 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](/docs/es/plugins/mods/events#the-order-mods-run-in). 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](/docs/es/plugins/mods/admin#enforce-a-policy-with-a-mod-of-your-own) primero. Verifica que el mod de política rechace un mod que inicia un proceso y admita uno que no:

```typescript acme-guard/tests/guard.test.ts theme={null}
import { expect, test, tier } from 'claude-code/testing'

// Load the mod under test ahead of every other mod
tier('prepend')

// A second mod whose code calls $.process.run, which the policy blocks
const runner = {
  name: 'runner',
  register(on) {
    on('tool.call', async ($, e, next) => {
      await $.process.run(['ls'])
      return { result: 'runner answered' }
    })
  },
}

// A second mod that calls nothing the policy blocks
const reader = {
  name: 'reader',
  register(on) {
    on('tool.call', async ($, e, next) => {
      return { result: 'reader answered' }
    })
  },
}

test('refuses a mod that starts a process', { plugins: [runner] }, async ($, on) => {
  on('tool.call', () => ({ result: 'claude code answered' }))
  let message = ''
  try {
    // The first call on $ loads the mods, so the refusal is thrown here
    await $.tool.call({ tool: 'Bash', command: 'ls' })
  } catch (error) {
    message = error.message
  }
  expect(message).toBe('runner: refused by acme-guard: Acme policy: mods may not call process.run')
})

test('admits a mod that starts no process', { plugins: [reader] }, async ($, on) => {
  on('tool.call', () => ({ result: 'claude code answered' }))
  const out = await $.tool.call({ tool: 'Bash', command: 'ls' })
  // The answer comes from reader, which shows that it loaded
  expect(out).toEqual({ result: 'reader answered' })
})
```

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.

<h2 id="next-steps">
  Próximos pasos
</h2>

* [Soluciona problemas de un mod](/docs/es/plugins/mods/troubleshoot): descubre por qué un mod no hace nada en una sesión
* [Referencia de mods](/docs/es/plugins/mods/reference): cada evento de entrada y resultado, para escribir stubs
