WebMock adapter: body.present? returns false even when body has content after v1.7.1
After upgrading from httpx 1.7.0 to 1.7.1, the ResponseBodyMethods override in the WebMock adapter causes body.present? to return false even when the response body contains data. This breaks code that checks for body presence before parsing.
Environment
- httpx version: 1.7.1
- Ruby version: 3.4.8
- WebMock: Enabled (using httpx's built-in adapter)
- Test framework: Minitest with VCR
Steps to Reproduce
require 'webmock'
require 'httpx'
require 'httpx/adapters/webmock'
WebMock.enable!
WebMock.disable_net_connect!
# Stub a request with JSON error response
WebMock.stub_request(:post, "https://example.com/api")
.to_return_json(
status: 429,
body: { error: { message: "Too many requests." } }
)
# Make the request
response = HTTPX.post("https://example.com/api")
# Check body state
puts "response.status: #{response.status}" # => 429
puts "response.body.class: #{response.body.class}" # => HTTPX::Response::Body(plugin)/WebMock::HttpLibAdapters::Plugin
puts "response.body.present?: #{response.body.present?}" # => false ❌ (UNEXPECTED)
puts "response.body.empty?: #{response.body.empty?}" # => true ❌ (UNEXPECTED)
puts "response.body.to_s: '#{response.body.to_s}'" # => '{"error":{"message":"Too many requests."}}' ✓ (CORRECT)
puts "response.body.to_s.empty?: #{response.body.to_s.empty?}" # => false ✓ (CORRECT)
Expected Behavior
When a mocked response has body content, body.present? should return true.
Actual Behavior
body.present? returns false even though body.to_s returns the correct content.
Root Cause
In httpx 1.7.1, the @length tracking was moved from write() into decode_chunk():
Before (v1.7.0)
def write(chunk)
chunk = decode_chunk(chunk)
size = chunk.bytesize
@length += size # ← @length updated in write()
transition(:open)
@buffer.write(chunk)
...
end
def decode_chunk(chunk)
@inflaters.reverse_each { |inflater| chunk = inflater.call(chunk) }
chunk
end
After (v1.7.1) ref
def write(chunk)
chunk = decode_chunk(chunk) # ← @length now updated inside here
transition(:open)
@buffer.write(chunk)
...
end
def decode_chunk(chunk)
@inflaters.reverse_each { |inflater| chunk = inflater.call(chunk) }
@length += chunk.bytesize # ← MOVED HERE
chunk
end
The Problem
The WebMock adapter overrides decode_chunk to skip inflation for mocked responses ref:
module ResponseBodyMethods
def decode_chunk(chunk)
return chunk if @response.mocked? # ← Returns early!
super # ← Never reaches parent's @length increment
end
end
The Chain Reaction
- WebMock's
decode_chunkreturns early for mocked responses - Parent's
@length += chunk.bytesizenever executes -
@lengthstays at 0 -
empty?returns true (checks@length.zero?) -
present?returnsfalse(calls!blank?→empty?) - Code that checks
body.present?fails to parse the body
Meanwhile: The actual content is still written to @buffer (line 139 of webmock.rb), so body.to_s works fine.
Workaround
As a workaround, I do body.to_s.present? instead of body.present?
Additional Context
The issue specifically affects error responses (4xx, 5xx) in tests that use WebMock, where the response body contains error details that need to be parsed. The body content is present and accessible via to_s, but standard presence checks fail.
This creates an inconsistency where the same code behaves differently in tests (with WebMock) versus production (real HTTP responses).