The project is in a healthy, maintained state
Fabricate Anthropic Ruby SDK response objects — messages, content blocks, tool_use blocks, and streams — for tests that stub the client directly and return canned responses, without hitting the network or wrestling the real SDK's typed models. Also serializes those objects to/from plain hashes, which is how the deja gem records and replays them. Part of the llm_mock family.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

~> 3.0

Runtime

>= 0.1
 Project Readme

llm_mock_anthropic

When you test Ruby code that calls the Anthropic API, you usually don't want it hitting the network. One clean way to avoid that is to stub your Anthropic client and return a canned response object — but that runs into a wall: constructing a realistic Anthropic SDK response by hand is genuinely painful. A real Anthropic::Message needs id, model, role, type, usage, stop_reason, and typed content blocks; tool calls are nested; and streaming responses have no simple object to fake at all.

llm_mock_anthropic gives you small, ergonomic stand-ins for exactly those response shapes — messages, text blocks, tool_use blocks, and streams — so your stub can return something your code happily consumes:

allow(client.messages).to receive(:create).and_return(
  LlmMock::Anthropic.message([
    LlmMock::Anthropic.tool_use(id: "tu_1", name: "save_summary", input: {"text" => "…"}),
  ])
)

Why object-level (and when not to)

The common community approach is to stub at the HTTP layer (VCR/WebMock): record real HTTP and let the SDK deserialize it. That's a great fit when you can make a real call once. Stub at the object layer — what this gem is for — when that's awkward, most often because:

  • Streaming. Replaying SSE streams through VCR is fiddly; returning a Stream double is trivial.
  • You want to script the model's behavior deterministically (e.g. "this turn calls the complete tool") without recording anything.

If you want to record real calls once and replay them rather than hand-script responses, see deja — it builds on this gem.

Installation

# Gemfile
group :test do
  gem "llm_mock_anthropic"
end

What you get

Response value objects (duck-typed to the SDK's response surface — .content, block fields, .text, .accumulated_message):

Builder Returns Shape
LlmMock::Anthropic.text(str) TextBlock .type, .text
LlmMock::Anthropic.tool_use(id:, name:, input:) ToolUseBlock .type, .id, .name, .input
LlmMock::Anthropic.message(blocks) Message .content
LlmMock::Anthropic.stream(text_chunks:, message:) Stream .text, .accumulated_message

The structs are also available directly (LlmMock::Anthropic::Message.new(...)) if you prefer.

Example: a streamed tutor turn that ends by calling a tool

Say the code under test streams a reply and finishes when the model calls a complete tool — it calls messages.stream, renders the incremental text, then inspects the final message:

class TutorTurn
  def run(client, conversation)
    stream = client.messages.stream(
      model: "claude-sonnet-4-5",
      max_tokens: 1024,
      messages: conversation,
      tools: [ complete_tool ],
    )

    stream.text.each {|chunk| broadcast(chunk) } # render text as it arrives

    stream.accumulated_message.content.each do |block|
      finish! if block.type == :tool_use && block.name == "complete"
    end
  end
end

In a test, return a fake stream so that code runs without the network — one text chunk plus a final message that includes the complete tool call:

fake = LlmMock::Anthropic.stream(
  text_chunks: [ "Here's the core idea. " ],
  message: LlmMock::Anthropic.message([
    LlmMock::Anthropic.text("Here's the core idea. "),
    LlmMock::Anthropic.tool_use(id: "tu_done", name: "complete", input: {}),
  ]),
)
allow(client.messages).to receive(:stream).and_return(fake)

stream.text yields the chunks and stream.accumulated_message.content is the final block list — exactly the surface TutorTurn#run reads from a real stream.

For tool authors

LlmMock::Anthropic::Provider implements the llm_mock contract — it builds a stub client routed through a responder, invokes the real client, and serializes/deserializes responses to/from plain hashes. That's how deja uses this gem to record and replay Anthropic calls. You don't need any of that to use the builders above.

License

MIT — see LICENSE.