ClaudeCode.Test (ClaudeCode v0.36.3)
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.
Calls a tool on an MCP tool server module by name and returns the result.
Lists all tools registered on an MCP tool server module.
Sends a raw JSONRPC request to an MCP tool server module.
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
Calls a tool on an MCP tool server module by name and returns the result.
Builds the JSONRPC message internally so tests only need to provide the tool name and arguments.
Parameters
server_module- A module that usesClaudeCode.MCP.Servertool_name- The name of the tool to callarguments- A map of arguments to pass to the tool (string keys)opts- Optional keyword list::assigns- Map of assigns passed to the tool viaframe.assigns
Examples
result = ClaudeCode.Test.mcp_call_tool(MyApp.Tools, "get_weather", %{"latitude" => 37.7, "longitude" => -122.4})
assert %{"content" => [%{"type" => "text", "text" => text}]} = result
assert text =~ "Temperature"
# With assigns
result = ClaudeCode.Test.mcp_call_tool(MyApp.Tools, "get_db_record", %{"id" => "123"}, assigns: %{repo: MyApp.Repo})
Lists all tools registered on an MCP tool server module.
Returns the list of tool definitions from a ClaudeCode.MCP.Server module,
useful for asserting that tools are correctly registered.
Examples
tools = ClaudeCode.Test.mcp_list_tools(MyApp.Tools)
assert [%{"name" => "get_weather", "inputSchema" => %{}} | _] = tools
Sends a raw JSONRPC request to an MCP tool server module.
This is the escape hatch for testing MCP protocol details like initialization or notifications that aren't covered by the focused helpers.
Examples
message = %{"jsonrpc" => "2.0", "id" => 1, "method" => "initialize", "params" => %{}}
response = ClaudeCode.Test.mcp_request(MyApp.Tools, message)
assert %{"result" => %{"protocolVersion" => "2024-11-05"}} = response
@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 the internal test adapter 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.Init.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...")