Sign in or sign up before continuing. Don't have an account yet? Register now to get started.
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
```ruby
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](https://gitlab.com/os85/httpx/-/blob/v1.7.1/lib/httpx/response/body.rb#L204-211)
```
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](https://gitlab.com/os85/httpx/-/blob/v1.7.1/lib/httpx/adapters/webmock.rb#L92-98):
```
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
1. WebMock's `decode_chunk` returns early for mocked responses
2. Parent's `@length += chunk.bytesize` never executes
3. `@length` stays at 0
4. `empty?` returns true (checks `@length.zero?`)
5. `present?` returns `false` (calls `!blank?` → `empty?`)
6. 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).
issue