Mock dependencies from your tests
Run the existing agent. Replace its surroundings, not its decisions. A drill can control starting data, tool responses, failures and scheduled events; the agent still chooses its actions. A browser test may use Playwright to drive the agent’s UI. A worker or CLI agent does not need a browser. Keep production code unchanged. Your existing test runner installs replacements; Firedrill supplies the stateful world and records what happened.Replace an imported function
For a target declared askind: external with bindings: [direct], use
mockTool from @firedrill/sdk/testing as a mock implementation. This Vitest
example assumes the existing agent imports saveRecord from records-client.
The test imports Firedrill; the application does not.
output(value) to preserve a different native return shape. Use mode: sync
for a synchronous function or mode: async for a Promise. A failed operation
throws/rejects with ToolMockError, including envelope and status. Supply
error(envelope) to construct the dependency’s native Error class instead.
Choose idempotency keys according to the real operation: retries of one logical
request reuse a key; distinct requests use distinct keys. A repeated successful
key returns its receipt before selecting a new override.
Jest’s mocks or spies can install the same callable. Follow your runner’s module
loading rules: install replacements before importing the agent when required by
ESM. A spy must replace its implementation; spying alone can still call the real
service. Restore spies after tests. Module replacements must not be shared by
overlapping drills: use concurrency: 1 within each worker and non-concurrent
tests that share a module. Parallel trials need separate isolated processes or
module instances, not just separate test workers running concurrent trials.
A retained mockTool callable rejects after its binding expires.
Return a value or fail selected calls
AddtoolOverrides to your world, scenario or drill JSON/YAML. For a single test,
put the same rules in runDrills({ drill, setup: { scenario: { toolOverrides } } }).
For example, a Tool that declares a BUSY error can fail its first matching call:
return: returnvaluewithout running the Tool handler or its faults. It does not pretend a state change happened. The value must match the output schema.error: return a declared Tool error without running the handler or faults.original: use normal synthetic Tool behavior, including its active faults, instead of a lower-priority stub. This never means calling the production service.
actorId or supplied argument keys. Each supplied
argument value matches exactly, including nested objects and arrays; other
top-level arguments may differ.
Priority is world baseline → named scenario → drill → per-test setup. Within a
scope, later matching rules win. A higher-scope rule with the same id replaces
the lower rule entirely. Once its times limit is consumed, matching continues
to other rules, then normal Tool behavior—not the replaced definition. Omit
times for unlimited matches. Inline scenarios remain complete starting setups,
not overlays of the world baseline.
State, reset and evidence
For a replacement that changes records, emits events or schedules consequences, author a deterministic Tool behavior module. Reuse it normally or select it withsetup.tools.behaviorOverrides. mockTool calls that same engine, so function
mocks and protocol clients share state and evidence.
An override’s identity, source scope, selected outcome and match count are recorded
with the operation. One-shot consumption is durable even when the selected Tool
handler rolls back. Whole-world reset restores initial rule counts with the other
state; package reset restores counts only for those packages. Restoring runner
mocks and resetting a Firedrill world are separate operations.
Per-test rules are compiled into the derived build and retained in its report.
Reproduction with the exact build and seed restores those rules. Reinstall your
test-side function adapters using the same harness; the build does not capture
arbitrary test closures, application code or model randomness.
Interception boundaries
This is not a universal process hook. Module mocking can replace imported functions, SDK methods and provider-router calls exposed to your test runner. It cannot automatically replace private same-module calls, previously captured references, subprocess internals or native built-ins inside an opaque agent. For another process, use its existing MCP/HTTP endpoint configuration, a supported CLI adapter, or an explicit test-only process harness. Filesystem/grep behavior needs an interceptable function boundary or a controlled fixture workspace; changing a working directory alone does not confine filesystem access.mockTool never falls through to a real dependency. Other dependencies remain
your harness’s responsibility: block unintended network/process/file access and
verify the selected binding before allowing side-effecting real credentials.