Summary
A test framework that lets developers verify hooks without launching Claude Code.
Motivation
Currently, the only way to test a hook is to run Claude Code and trigger the event manually. This makes the development cycle slow and edge-case coverage impractical.
Proposed API
hx test # run all extension tests
hx test guard --event PreToolUse # simulate a specific event
Key Features
- Mock event payload factories for every event type (with
tool_input as Record<string, any>)
- Assertions on
HookOutput — verify deny(), allow(), addContext() results without checking wire format
HookBlockError verification (exit code 2)
- LLM response mocking for prompt/agent hooks
- Snapshot testing support
hx new templates include a test file by default
Example
import { testHook, mockEvent } from "@dawkinsuke/hooks/testing"
import { deny } from "@dawkinsuke/hooks"
test("blocks rm -rf /", async () => {
const event = mockEvent("PreToolUse", "Bash", {
input: { command: "rm -rf /" },
})
const result = await testHook(guardExtension, event)
// Can assert on HookOutput directly
expect(result).toBeInstanceOf(HookOutput)
expect(result._decision).toBe("deny")
})
test("allows safe commands", async () => {
const event = mockEvent("PreToolUse", "Bash", {
input: { command: "ls -la" },
})
const result = await testHook(guardExtension, event)
expect(result).toBeUndefined() // pass-through
})
Design Notes
mockEvent() should produce payloads matching HookEventMap types (with relaxed tool_input: Record<string, any>)
testHook() returns the raw handler result (HookOutput | HookJSONOutput | void), not wire-format JSON
- For wire-format assertions, use
result._resolve("PreToolUse") on HookOutput instances
- Should integrate with
isHookOutput() for type-safe assertions
Summary
A test framework that lets developers verify hooks without launching Claude Code.
Motivation
Currently, the only way to test a hook is to run Claude Code and trigger the event manually. This makes the development cycle slow and edge-case coverage impractical.
Proposed API
Key Features
tool_inputasRecord<string, any>)HookOutput— verifydeny(),allow(),addContext()results without checking wire formatHookBlockErrorverification (exit code 2)hx newtemplates include a test file by defaultExample
Design Notes
mockEvent()should produce payloads matchingHookEventMaptypes (with relaxedtool_input: Record<string, any>)testHook()returns the raw handler result (HookOutput | HookJSONOutput | void), not wire-format JSONresult._resolve("PreToolUse")onHookOutputinstancesisHookOutput()for type-safe assertions