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

# モッドをテストする

> イベントを発生させ、Claude Code の回答をスタブし、ボタンを押すモッドの自動テストを書きます。セッション、サインイン、ネットワークは不要です。

モッドの自動テストを書いて、シェルから [`claude plugin test`](/docs/ja/plugins/mods/reference#commands) で実行できます。テストはフックが処理するイベントを発生させ、フックが何をしたかをチェックするため、セッションに到達する前に問題を見つけることができます。最初の例は [モッドを作成する](/docs/ja/plugins/mods/create) のモッドをテストします。

<h2 id="write-a-test">
  テストを書く
</h2>

テストはモッドを読み込み、Claude Code が行うようにイベントをフックを通して送信し、セッション、サインイン、ネットワークなしでフックが何をしたかをチェックします。テストはシェルから `claude plugin test` で実行し、各テストファイルはテストキット（`claude-code/testing` モジュール内のテストライブラリ）をインポートします。

各テストファイルに `.test.ts` で終わる名前（例：`first-mod.test.ts`）を付け、プラグインディレクトリ内のどこかに保存します。すべてのテストファイルには少なくとも 1 つの `test()` が必要です。そうでないと、`declares no test(): nothing ran` で実行が失敗します。テストファイルはモッド自体のファイルと兄弟の `.ts` ヘルパーをインポートできるため、ゲームのルールなどのプレーン関数をキットなしでユニットテストできます。

このテストは 2 つのツール呼び出しを発生させ、[モッドを作成する](/docs/ja/plugins/mods/create) の `/tally` コマンドを実行し、返信が両方をカウントしていることをチェックします。最初の行は [スタブ](#stub-what-claude-code-would-answer) で、Claude Code の代わりにツール呼び出しに答えます。`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')
})
```

シェルで `first-mod` ディレクトリからテストを実行します：

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

出力は各テストとそれが成功したかどうかを名前で示し、実行ごとに異なるタイミングを表示します：

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

各 `$.tool.call` はモッドの [`tool.call`](/docs/ja/plugins/mods/reference#tools) フックを通過し、カウントに 1 を追加してスタブに呼び出しを渡しました。`ls` は実行されず、ファイルは読み込まれませんでした。`$.command.run` はモッドの [`command.run`](/docs/ja/plugins/mods/reference#commands-and-configuration) フックに移動し、`answer` はそのフックが返したオブジェクトです。

テストが失敗すると、コマンドはステータス 1 で終了するため、CI で機能します。独自のモッドがそれを実行するシェルで読み込めない場合、`claude plugin test: hooks modules are turned off` で始まる行と理由を出力し、ステータス 1 で終了します。

<h3 id="stub-what-claude-code-would-answer">
  Claude Code が答えるものをスタブする
</h3>

テストではモデル、ストア、またはツールが実行されないため、モッドが Claude Code の回答を期待する場所では、テストはスタブで回答を提供します。テスト関数はそのために 2 つの引数を受け取ります：

* **`$`**: テスト独自の `$` で、Claude Code が立つ場所に立ちます。これはフックが受け取る [mods API](/docs/ja/plugins/mods/reference#mods-api-methods) ではありません。各メソッドは同じ名前のイベントを発生させ、モッドのフックを通して送信し、結果に解決します：`$.tool.call({ tool: 'Bash', command: 'ls' })` は `tool.call` を発生させます。`$.command.run`、`$.prompt.submit`、`$.session.start`、`$.turn.complete` は同じように機能し、`$.classic.Stop` と他の `$.classic` メソッドは [設定フックイベント](/docs/ja/plugins/mods/events#hook-the-settings-hook-events) を発生させます。テストは `ui.close` などの mods API 呼び出しを直接発生させることはできません。モッドを通してトリガーします。例えば、ペインを閉じるボタンを押します。
* **`on`**: スタブを登録するために呼び出します。スタブは Claude Code の代わりに答えるフックです。mods API 呼び出しの `$.` なしでスタブに名前を付けます。そのため、`store.get` として登録されたスタブはモッドの `$.store.get` に答えます。モッドが [`$.model.complete`](/docs/ja/plugins/mods/api#call-a-model) または [`$.store.get`](/docs/ja/plugins/mods/interface#keep-state) を呼び出すと、スタブが回答を提供します。

この例はモデル呼び出しをスタブします。フックは `grader` という名前のモッドに属し、文をモデルに送信して返信が `PASS` で始まるかどうかを報告する `/grade` コマンドを処理します。ファイルはテスト中のフックのみを保持するため、モッドは [モッドを作成する](/docs/ja/plugins/mods/create#write-a-mod-yourself) のように `plugin.json` と `hooks.json` も必要です。セッションで `/grade` と入力するには、モッドは [コマンドを登録](/docs/ja/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' }
  })
}
```

このテストはモデル呼び出しをスタブして、フックが成功した返信で何をするかをチェックします：

```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')
})
```

テストは成功します。フックの `reply` は `value` の下のオブジェクトで、その `text` は `PASS` で始まるためです。他のブランチをチェックするには、スタブが `FAIL` で始まる `text` を返す 2 番目のテストを追加し、`Try again` を期待します。

mods API 呼び出しのスタブは `value` フィールドを持つオブジェクトを返します。これはモッドで呼び出しが解決するものを保持します：`{ value: 7 }` は `$.store.get` を `7` に解決させます。[`turn.step`](/docs/ja/plugins/mods/reference#turns) または `tool.call` などの Claude Code のイベントのスタブは、そのイベント独自の結果（`{ result: 'ok' }` など）を返します。`$.session.send` と `$.prompt.fill` はイベントの結果もテーブルが示すように取ります。[スタブが返すものを調べる](#look-up-what-a-stub-returns) は各一般的な名前がどの形式を取るかを示します。2 つのエラーはスタブが間違っているか不足していることを意味します。失敗したテストの出力には `the engine reported:` で始まるブロックが含まれ、各エラーがそこに表示されます：

* `returned neither { value } nor { deny }`: mods API 呼び出しのスタブが裸の値を返した
* `no implementation for` の後に名前が続く：モッドがその呼び出しを行い、スタブがそれに答えない

キットはまた、メモリ内モックをエクスポートします。これはネームスペース全体に答えます。`mock.clock(on)` は [`$.clock`](/docs/ja/plugins/mods/api#run-work-in-the-background) に答え、`mock.store(on, { count: 7 })` はそれらのエントリで始まるストアから `$.store` に答え、`mock.env(on, { CI: 'true' })` はそれらの変数から `$.env.get` に答えます。`mock.clock` はモッククロックを返し、テストはそれを進めるため、タイマーのテストは待機しません。`mock.store` は何も返さないため、モッドが何を保存したかをチェックするには、[描画テスト](#test-a-drawing) が行うように 2 つの `store` スタブを自分で書きます。

<h3 id="follow-the-test-kit’s-rules">
  テストキットのルールに従う
</h3>

テストキットには独自のルールがいくつかあり、1 つを破ると新しいテスト作成者が最初に遭遇するエラーが生成されます：

* **`$` の最初の呼び出しの前にすべてのスタブを登録します。** その後に `on` を呼び出すと、`on("ui.render") after the test first called $` などのエラーがスローされます。

* **[`session.start`](/docs/ja/plugins/mods/reference#session) は単独では実行されません。** 各テストはモジュールが新しく読み込まれた状態で開始され、フックは呼び出されないため、モジュールレベルの変数は初期値を保持します。フックが `session.start` が設定するものに依存する場合、最初にそれを発生させます：

  ```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' })
  ```

  2 番目のスタブは、`session.start` フック（例えば [チュートリアル](/docs/ja/plugins/mods/create#write-a-mod-yourself) のもの）が行う `$.command.register` 呼び出しに答えます。それなしでは、その呼び出しは `no implementation for command.register` で拒否され、キットはフックをスキップするため、フック内の呼び出しの後の何も実行されません。テストはその時点で失敗しません。スキップされたフックは、後のチェックが失敗した場合にのみ `the engine reported:` の下にリストされます。

* **`next(e)` を返すフックにはスタブが必要です。** 例えば、Claude がアイドル状態の間は何も描画しないために `next(e)` を返す [`ui.render`](/docs/ja/plugins/mods/reference#interface) フックは、[マウント](#test-a-drawing) が `no implementation for ui.render` で失敗します。プレーンデータとして要素を返すスタブを登録します：

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

  スタブが登録されると、マウントが成功し、`ui.find({ type: 'Text' })` はフックが `next(e)` を返すたびにその要素を返します。

* **`turn.step` のスタブは非同期ジェネレータです**。テストはストリームを最後まで読んで結果を取得します：

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

  ループが終了すると、`result` はスタブが返したオブジェクトで、モッドの `turn.step` フックが変更する機会を持った後です。ここで `result.answer` は `'ok'` です。

* **ツール呼び出しをツールの名前と引数をフィールドとして発生させます**。例えば `await $.tool.call({ tool: 'Bash', command: 'ls' })`、`{ result }` を返す `tool.call` スタブを登録します。

<h3 id="look-up-what-a-stub-returns">
  スタブが返すものを調べる
</h3>

モッドがテストで行う mods API 呼び出しはすべて、キットが自分で答える少数を除いて、スタブが答える必要があります：[`$.ui.invalidate`](/docs/ja/plugins/mods/interface#redraw-when-something-changes) と [`$.state`](/docs/ja/plugins/mods/interface#keep-state) 呼び出し。`$.clock` 呼び出しの場合、`mock.clock(on)` を使用するか、モッドの `$.clock.now()` は `no implementation for clock.now` で失敗します。

このテーブルはモッドが最も使用するものをリストします。最初の列はモッドが行う呼び出しまたは `next(e)` で渡すイベントです。2 番目はその名前の下で `on` に渡す関数です。そのため、`$.store.get` 行は `on('store.get', ($, e) => ({ value: saved.get(e.key) }))` になります。スタブ内の `'...'` は入力するテキストをマークします：

| モッドが呼び出すまたは渡すもの | スタブ |
| :- | :- |
| `$.command.register`、`$.tool.register`、`$.ui.toast`、`$.ui.log`、`$.ui.status`、`$.ui.close`、`$.store.set` | `() => ({ value: undefined })`。`ui.toast` と `ui.log` の場合、テキストは `e.text` です。 |
| `$.store.get` | `($, e) => ({ value: saved.get(e.key) })` |
| `$.fs.read` | `($, e) => ({ value: e.path.endsWith('notes.md') ? '# Notes' : '' })`。`e.path` は絶対パスとして到着するため、`endsWith` と比較します。 |
| `$.ui.open` | `() => ({ value: { isPlaced: true } })` |
| `$.ui.ask` | `tool.call` スタブ。質問は `AskUserQuestion` ツールへの呼び出しとして到着するため：`($, e) => ({ result: { answers: { [e.questions[0].question]: 'Run it' } } })`。モッドが他のツール呼び出しを渡す場合は最初に `e.tool` をチェックします。 |
| `$.model.complete` | `() => ({ value: { isAnswered: true, text: '...', usage } })` |
| `$.process.run` | `($, e) => ({ value: { exitCode: 0, stdout: '...', stderr: '' } })`。`e.argv` は引数リストで、`e.init` は `cwd` と `timeoutMs` を保持します。 |
| 失敗すべき mods API 呼び出し | `() => ({ deny: 'the reason' })`。これはモッドで呼び出しを拒否させます。スローするスタブはスキップされます。 |
| `session.start` | `() => ({ cwd: '/work' })` |
| `turn.start` | `($, e) => ({ turnId: e.turnId })` |
| `tool.call` | `() => ({ result: '...' })` |
| `turn.complete` | `() => ({ text: '' })`。`$.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` はモッドが `{ sessionId }` を渡した場合でも文字列として到着します。 |
| `session.receive` | `($, e) => ({ text: e.text })`。`$.session.receive({ origin: { kind: 'peer-send-message' }, text })` で発生させます。 |
| `ui.render` | `() => ({ type: 'Text', props: {}, children: ['...'] })` |

`expect` には `toBe`、`toEqual`、`toMatch`、`toMatchObject`、`toContain`、`toBeDefined`、`toBeUndefined`、`toThrow` のアサーションがあり、それらのいずれかの前に `.not` があります。

<h2 id="test-a-timer">
  タイマーをテストする
</h2>

タイマーで作業を実行するモッドには、テストが制御するクロックが必要です。そのため、テストは待機する代わりに時間を前に進めることができます。`const clock = mock.clock(on)` は `0` で開始し、テストが移動するときのみ移動するモッククロックを返します。別の時間で開始するには、ミリ秒で渡します。例えば `mock.clock(on, { now: 5000 })` のように。クロックには次のメソッドがあります：

| メソッド | 何をするか |
| :- | :- |
| `await clock.advance(1000)` | 時間をそのミリ秒数だけ前に移動し、期限が来たタイマーを実行します |
| `await clock.set(5000)` | 時間をその値に移動します。`advance` のように |
| `clock.now()` | 時間を返します。これはモッドの `$.clock.now()` が解決するものです |
| `await clock.settle()` | 既に期限が来ているタイマー（例えば、ゼロ遅延の `$.clock.after` 呼び出しのチェーン）を実行します。時間を移動しません |
| `await clock.sleep(2000)` | スタブ内で、テストがそこまで進むまでそのスタブのみが答えるようにします。これは遅いモデルまたはプロセスをシミュレートする方法です |

このフックは `countdown` という名前のモッドに属し、秒数を取る `/countdown` コマンドを処理し、1 秒の `$.clock.every` タイマーを開始し、ゼロでトーストを表示します。`grader` と同様に、ファイルはテスト中のフックのみを保持し、コマンドを登録しません：

```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 {}
  })
}
```

このテストは `/countdown 3` を実行し、モッククロックを移動するため、3 秒間の動作をチェックします。3 秒待つ必要はありません：

```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'])
})
```

最初の `expect` はトーストが早く来ないことを示し、2 番目はそれが 1 回来ることを示します。各 `advance` は期限が来たタイマーが実行された後に解決するため、次の行のチェックはそれらの効果を見ます。

<h2 id="test-a-drawing">
  描画をテストする
</h2>

テストはモッドの [レンダリングサイト](/docs/ja/plugins/mods/reference#render-sites) の 1 つを描画し、要素を押し、入力し、見つけることができます。`$.ui.mount` はサイトをモッドの `ui.render` フックを通して描画し、それぞれのメソッドを持つハンドルを返します。1 つのテストで複数のアプリをカバーするには、`surface` をアプリに設定して描画します。このテストは [タブを使用してペインを構築する](/docs/ja/plugins/mods/interface#build-a-pane-with-tabs) からペインを開き、タブを切り替え、ボタンを押し、ターミナルと Desktop アプリのカウントをチェックします：

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

シェルで `hello-tabs` ディレクトリから `claude plugin test` を実行します。テストは両方のアプリがカウント行を描画し、モッドが `2` を保存したときに成功します。最初のアプリから 2 番目のアプリへのカウントは、両方のマウントが同じ読み込まれたモジュールを使用するため、引き継がれます。

`$.ui.mount` が返すハンドルには次のメソッドがあり、モッドが与えた `key` で要素をアドレス指定します：

| メソッド | 何をするか |
| :- | :- |
| `press({ key: 'more' })` | その `key` を持つ `Button` を押します |
| `input({ key: 'new-note', text: 'buy milk' })` | テキストを `key` を持つ `Input` に入力し、Enter を押します。`kind: 'change'` を追加して、送信せずに入力します。 |
| `select({ key: 'size', value: 'large' })` | その `key` を持つ `Select` でその値を持つオプションを選択します |
| `find({ key: 'more' })` または `find({ type: 'Text', text: 'Count: 2' })` | 最初にマッチする要素を `{ type, props, children }` として返すか、`undefined` を返します。`text` は文字列または正規表現です。 |
| `unmount()` | 描画を削除します |

各メソッドはハンドラーが完了した後に解決するため、次の行で結果をチェックできます。`props` を Claude Code がそのサイトに渡すものに設定します。[レンダリングサイトテーブル](/docs/ja/plugins/mods/reference#render-sites) は各サイトの props をリストし、[ビルドのタイプ](/docs/ja/plugins/mods/create#get-the-types-for-your-build) はそれらのタイプを持ちます。

描画テストはフックが返すツリーをチェックし、そのアプリに対して有効かどうかをチェックします。アプリがそれをどのように描画するかはチェックしないため、実際のセッションで新しいレイアウトを見てください。

<h3 id="test-a-drawing-after-clear">
  `/clear` の後に描画をテストする
</h3>

各テストはすべての `$.state` 値がデフォルトで開始します。これは `/clear` がそれらを残す方法です。モッドが次に何をするかをテストするには、`session.start` をスキップし、`source: 'clear'` で `classic.SessionStart` を発生させ、モッドが描画するものをチェックします。

このテストは [保存された値を `/clear` の後に再度読み込む](/docs/ja/plugins/mods/interface#load-a-saved-value-again-after-clear) からモジュールをチェックします。[描画をテストする](#test-a-drawing) からファイルに追加します。ここで `PANE` が定義されています。そのファイルの最初のテストはボタンがカウントを保存することを期待します。[複数のセッションから保存する](/docs/ja/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()
})
```

テストは、モッドの `classic.SessionStart` フックがペインが描画される前に保存された `7` を `$.state` にコピーしたときに成功します。モジュールにそのフックがない場合、ペインは `Count: 0` を描画し、`find` は `undefined` を返し、テストは `toBeDefined` で失敗します。

<h2 id="test-a-mod-that-judges-other-mods">
  他のモッドを判断するモッドをテストする
</h2>

組織が [`prependPlugins`](/docs/ja/plugins/mods/admin) にリストするモッドは、別のモッドが読み込まれる前にそれを拒否できます。1 つをテストするには、モッドのティアを設定し、テストに 2 番目のモッドを与えて、モッドが許可または拒否します：

* **`tier`**: テストファイルの上部で 1 回呼び出します。例えば `tier('prepend')` のように。モッドを `prepend`、`append`、または `builtin` として読み込みます。これは [モッドが実行される順序](/docs/ja/plugins/mods/events#the-order-mods-run-in) でのその場所です。それなしでは、モッドは `user` として読み込まれます。
* **`plugins`**: テスト本体の前にテストにオプションオブジェクトを渡します。その `plugins` 配列は、`name` と `register` 関数を持つ、インラインで書いたモッドを保持します。別の場所に 1 つを読み込むには、`tier` を追加します。

このテストファイルは [管理ページからのポリシーモッド](/docs/ja/plugins/mods/admin#enforce-a-policy-with-a-mod-of-your-own) を最初に読み込みます。ポリシーモッドがプロセスを開始するモッドを拒否し、そうでないモッドを許可することをチェックします：

```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' })
})
```

シェルで `acme-guard` ディレクトリから `claude plugin test` を実行します。両方のテストは管理ページが示すようにポリシーモッドで成功します。

キットはテストの最初の `$` 呼び出しですべてのモッドを読み込みます。モッドが 1 つを拒否すると、その呼び出しはスローされ、メッセージは拒否されたモッド、それを拒否したモッド、および理由を名前で示します。2 番目のテストでは何も拒否されないため、`reader` はスタブに到達する前にツール呼び出しに答えます。

<h2 id="next-steps">
  次のステップ
</h2>

* [モッドをトラブルシューティングする](/docs/ja/plugins/mods/troubleshoot)：モッドがセッションで何もしない理由を見つけます
* [モッドリファレンス](/docs/ja/plugins/mods/reference)：スタブを書くための各イベントの入力と結果
