bblimke/webmock · error · WebMock::Response::InvalidBody
must be one of: #{valid_types}. '#{@body.class}' given.
Error message
must be one of: #{valid_types}. '#{@body.class}' given. What it means
The same response-body validator (lib/webmock/response.rb:124), non-Hash branch: the body is not nil, not one of Proc/IO/Pathname/String/Array, and not a Hash - e.g. Integer, Float, Symbol, true/false, OpenStruct or an arbitrary Struct - so it raises WebMock::InvalidBody listing the accepted classes. Unlike the Hash case there is no serialization hint, because there is no obvious encoding for the value; the fix is always to convert it to a string (or stream it via IO).
Source
Thrown at lib/webmock/response.rb:124
def stringify_body!
if @body.is_a?(IO) || @body.is_a?(Pathname)
io = @body
@body = io.read
io.close if io.respond_to?(:close)
end
end
def assert_valid_body!
valid_types = [Proc, IO, Pathname, String, Array]
return if @body.nil?
return if valid_types.any? { |c| @body.is_a?(c) }
if @body.is_a?(Hash)
raise InvalidBody, "must be one of: #{valid_types}, but you've used a #{@body.class}. " \
"Please convert it by calling .to_json .to_xml, or otherwise convert it to a string."
else
raise InvalidBody, "must be one of: #{valid_types}. '#{@body.class}' given."
end
end
def read_raw_response(io)
socket = ::Net::BufferedIO.new(io)
response = ::Net::HTTPResponse.read_new(socket)
transfer_encoding = response.delete('transfer-encoding') #chunks were already read by curl
response.reading_body(socket, true) {}
options = {}
options[:headers] = {}
response.each_header {|name, value| options[:headers][name] = value}
options[:headers]['transfer-encoding'] = transfer_encoding if transfer_encoding
options[:body] = response.read_body
options[:status] = [response.code.to_i, response.message]
options
ensure
socket.closeView on GitHub (pinned to b187df8827)
Solutions
- Convert to a string: .to_return(body: 42.to_s) - or body: 'true' / body: 'ok'
- If the endpoint is JSON, use .to_return_json(body: { count: 42 })
- For file or streaming responses, pass a File/IO or Pathname - those are supported as-is
Example fix
# before stub_request(:get, 'https://api.example.com/count').to_return(body: 42) # WebMock::InvalidBody: 'Integer' given # after stub_request(:get, 'https://api.example.com/count').to_return(body: '42')
Defensive patterns
Strategy: type-guard
Validate before calling
body = [Proc, IO, Pathname, String, Array].any? { |c| raw.is_a?(c) } || raw.nil? ? raw : raw.to_s
stub.to_return(body: body) Type guard
def valid_response_body?(b)
b.nil? || [Proc, IO, Pathname, String, Array].any? { |c| b.is_a?(c) }
end Try / catch
begin stub.to_return(body: raw) rescue WebMock::InvalidBody stub.to_return(body: raw.to_s) end
Prevention
- Always .to_s or .to_json fixture payloads before passing them as bodies
- Centralize response building in one helper that enforces string bodies
- Assert on body type in spec helpers before registering stubs
When it happens
Trigger: .to_return(body: 42) for a counter endpoint; .to_return(body: :ok); passing an OpenStruct or Struct built by a fixture factory; passing a boolean such as body: result.present?.
Common situations: Numeric or text responses passed as native types from test data; fixture builders returning Structs instead of strings; quick one-off stubs using whatever value the test computed.
Related errors
- The basic_auth option value should be an array which contain
- URI should be a String, Regexp, Addressable::Template, a cal
- #to_return_json does not support passing a block
- must be one of: #{valid_types}, but you've used a #{@body.cl
- Real HTTP connections are disabled. Unregistered request: #{
AI-assisted analysis of bblimke/webmock@b187df8827 (2026-08-23).
Data as JSON: /api/errors/d2b9ed290423d1a4.
Report an issue: GitHub.