webmock icon indicating copy to clipboard operation
webmock copied to clipboard

Stub request with hash_including

Open leoplct opened this issue 3 years ago • 4 comments

I am trying to match this url

GET http://example.com/?a=1&b=2&x=1

but it fails. Here a reproducible error.

WebMock.disable_net_connect!
stub_request(:get, "example.com").with(query: hash_including({"x" => 1}))
Net::HTTP.get(URI('http://example.com/?a=1&b=2&x=1'))` 

The error says:

Failure/Error: Net::HTTP.get(URI('http://example.com/?a=1&b=2&x=1'))
     
     WebMock::NetConnectNotAllowedError:
       Real HTTP connections are disabled. Unregistered request: GET http://example.com/?a=1&b=2&x=1 with headers {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Host'=>'example.com', 'User-Agent'=>'Ruby'}

You can stub this request with the following snippet:

     
stub_request(:get, "http://example.com/?a=1&b=2&x=1").
         with(
           headers: {
          'Accept'=>'*/*',
          'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
          'Host'=>'example.com',
          'User-Agent'=>'Ruby'
           }).
         to_return(status: 200, body: "", headers: {})
     
       registered request stubs:
     
       stub_request(:get, "http://example.com/ with query params hash_including({"x"=>1})")

leoplct avatar Jan 26 '22 17:01 leoplct

I found the problem is due to "integer" comparison vs "String"

This works. stub_request(:get, "example.com").with(query: hash_including({"a" => "1"})) p Net::HTTP.get(URI('http://example.com/?a=1&b=2&x=1'))

leoplct avatar Jan 27 '22 09:01 leoplct

I think it makes sense to automatically cast everything to string before doing the Hash diff

leoplct avatar Jan 28 '22 13:01 leoplct

I came across this issue yesterday and it took me a while to debug.

After playing around with the Webmock code, I don't think there's a clear solution to fix hash_including directly—since it's defined in RSpec and changing it will undoubtably break other specs that use that method outside of Webmock params.

@bblimke Do you think it would be helpful to add a separate method for this purpose? Something like

def query_hash_including(*args)
  args = args.map { |arg| arg.is_a?(Hash) ? cast_numeric_hash_values_as_strings(arg) : arg }
  hash_including(*args)
end

def cast_numeric_hash_values_as_strings(hash)
  hash.transform_values { |value| value.is_a?(Numeric) ? value.to_s : value }
end

That might allow Webmock users to use integers, floats, or strings in query param expectations, without breaking anything

stub_request(:get, "example.com").with(query: query_hash_including({"a" => "1"}))

Bodacious avatar Jan 11 '23 11:01 Bodacious

@Bodacious thank you for providing the suggested solution.

I wish HashIncludingMatcher in RSpec provided a public method to get the @expected Hash. We could then check if the Hash contains any non String values and raise an argument error, explaining the user, to only match the query against a a hash with string values.

I feel it's more intuitive to use hash_including rather than custom query_hash_including. In addition to that people should learn, what is the difference, when they use query_hash_including. Perhaps support for hash_including should be deprecated and deprecation message could explain that it needs to be replaced with query_hash_including

bblimke avatar Feb 06 '24 16:02 bblimke