ClaudeCode.Test (ClaudeCode v0.16.0)
View SourceReq.Test-style test helpers for ClaudeCode.
This module provides a simple way to mock Claude responses in your tests, following the same patterns as Req.Test.
Setup
Configure the adapter in your test environment:
# config/test.exs config :claude_code, adapter: {ClaudeCode.Test, ClaudeCode}In your test helper, start the ownership server:
# test/test_helper.exs ExUnit.start() Supervisor.start_link([ClaudeCode.Test], strategy: :one_for_one)Register stubs in your tests:
test "returns greeting" do ClaudeCode.Test.stub(ClaudeCode, fn _query, _opts -> [ ClaudeCode.Test.text("Hello! How can I help?"), ClaudeCode.Test.result() ] end) {:ok, session} = ClaudeCode.start_link([]) result = session |> ClaudeCode.stream("Hi") |> ClaudeCode.Stream.final_text() assert result == "Hello! How can I help?" end
Message Helpers
text/2- Creates an assistant message with text contenttool_use/3- Creates a tool invocation messagetool_result/2- Creates a tool result messagethinking/2- Creates a thinking block messageresult/2- Creates the final result messagesystem/1- Creates a system initialization message
Async Tests
This module uses NimbleOwnership for process-based isolation, allowing
concurrent test execution. Stubs registered in a test process are only
visible to that process and its allowees.
To allow a spawned process to access stubs:
ClaudeCode.Test.allow(ClaudeCode, self(), pid_of_spawned_process)Using Different Names
The name in {ClaudeCode.Test, name} can be any term. This is useful when
you need different stub behaviors in the same test, or when building wrapper
modules around ClaudeCode:
# Testing multiple "agents" with different behaviors
ClaudeCode.Test.stub(MyApp.CodingAgent, fn _query, _opts ->
[ClaudeCode.Test.text("Here's the code...")]
end)
ClaudeCode.Test.stub(MyApp.ResearchAgent, fn _query, _opts ->
[ClaudeCode.Test.text("Based on my research...")]
end)
{:ok, coder} = ClaudeCode.start_link(adapter: {ClaudeCode.Test, MyApp.CodingAgent})
{:ok, researcher} = ClaudeCode.start_link(adapter: {ClaudeCode.Test, MyApp.ResearchAgent})
Summary
Functions
Allows pid_to_allow to access stubs owned by owner_pid.
Creates a final result message.
Sets the mode to shared global.
Returns a list of messages from the registered stub.
Registers a stub for the given name.
Creates a system initialization message.
Creates an assistant message with text content.
Creates an assistant message with a thinking block.
Creates a user message with a tool result block.
Creates an assistant message with a tool use block.
Functions
Allows pid_to_allow to access stubs owned by owner_pid.
This is useful when you spawn processes that need to access the same stubs as the test process.
Example
test "spawned process can use stub" do
ClaudeCode.Test.stub(ClaudeCode, fn _, _ -> [...] end)
task = Task.async(fn ->
# This task can now access the stub
{:ok, session} = ClaudeCode.start_link([])
ClaudeCode.stream(session, "hi") |> Enum.to_list()
end)
# Allow the task to access our stubs
ClaudeCode.Test.allow(ClaudeCode, self(), task.pid)
Task.await(task)
end
@spec result( String.t(), keyword() ) :: ClaudeCode.Message.ResultMessage.t()
Creates a final result message.
Options
:is_error- Whether this is an error result (default: false):subtype- Result subtype (default: :success or :error_during_execution):session_id- Session ID (default: auto-generated):duration_ms- Duration in milliseconds (default: 100):num_turns- Number of turns (default: 1)
Examples
ClaudeCode.Test.result()
ClaudeCode.Test.result("Task completed successfully")
ClaudeCode.Test.result("Rate limit exceeded", is_error: true)
Returns a list of messages from the registered stub.
Called by ClaudeCode.Adapter.Test to retrieve stub messages.
The optional callers argument allows passing the caller chain from
a different process (used by the test adapter).
@spec stub( name :: term(), fun_or_messages :: (String.t(), keyword() -> [term()]) | [term()] ) :: :ok
Registers a stub for the given name.
The stub can be either a function or a list of messages:
Function stub
Receives the query and options, returns a list of messages:
ClaudeCode.Test.stub(ClaudeCode, fn query, opts ->
[
ClaudeCode.Test.text("Response to: #{query}"),
ClaudeCode.Test.result()
]
end)Static stub
A list of messages that will be returned for any query:
ClaudeCode.Test.stub(ClaudeCode, [
ClaudeCode.Test.text("Static response"),
ClaudeCode.Test.result()
])
@spec system(keyword()) :: ClaudeCode.Message.SystemMessage.t()
Creates a system initialization message.
Options
:session_id- Session ID (default: auto-generated):model- Model name (default: "claude-sonnet-4-20250514"):tools- List of available tools (default: []):cwd- Current working directory (default: "/test")
Examples
ClaudeCode.Test.system()
ClaudeCode.Test.system(model: "claude-opus-4-20250514", tools: ["Read", "Edit"])
@spec text( String.t(), keyword() ) :: ClaudeCode.Message.AssistantMessage.t()
Creates an assistant message with text content.
Options
:session_id- Session ID (default: auto-generated):stop_reason- Stop reason atom (default: nil):message_id- Message ID (default: auto-generated)
Examples
ClaudeCode.Test.text("Hello world!")
ClaudeCode.Test.text("Done", stop_reason: :end_turn)
@spec thinking( String.t(), keyword() ) :: ClaudeCode.Message.AssistantMessage.t()
Creates an assistant message with a thinking block.
Options
:signature- Thinking signature (default: auto-generated):text- Optional text to include after thinking:session_id- Session ID (default: auto-generated)
Examples
ClaudeCode.Test.thinking("Let me analyze this step by step...")
ClaudeCode.Test.thinking("First...", text: "Here's my answer")
@spec tool_result( String.t() | map(), keyword() ) :: ClaudeCode.Message.UserMessage.t()
Creates a user message with a tool result block.
The content can be a string or a map. Maps are automatically JSON-encoded.
Content is wrapped as a list of content blocks: [%{"type" => "text", "text" => content}]
Options
:tool_use_id- ID of the tool use this is responding to (default: nil for auto-linking):is_error- Whether the tool execution failed (default: false):session_id- Session ID (default: auto-generated)
Examples
ClaudeCode.Test.tool_result("file contents here")
ClaudeCode.Test.tool_result("Permission denied", is_error: true)
ClaudeCode.Test.tool_result(%{status: "success", data: [1, 2, 3]})
@spec tool_use(String.t(), map(), keyword()) :: ClaudeCode.Message.AssistantMessage.t()
Creates an assistant message with a tool use block.
Options
:id- Tool use ID (default: auto-generated):text- Optional text to include before the tool use:session_id- Session ID (default: auto-generated)
Examples
ClaudeCode.Test.tool_use("Read", %{path: "/tmp/file.txt"})
ClaudeCode.Test.tool_use("Bash", %{command: "ls -la"}, text: "Let me check...")