webmock icon indicating copy to clipboard operation
webmock copied to clipboard

Response body can't be a hash

Open Morred opened this issue 10 years ago • 16 comments

When I stub a request and specify a hash for the response body, I get the following error:

WebMock::Response::InvalidBody:
    must be one of: [Proc, IO, Pathname, String, Array]. 'Hash' given

Is there a particular reason why it can't be a hash?

Morred avatar Jan 30 '15 06:01 Morred

Please have a look at https://github.com/bblimke/webmock/pull/427

If response body is declared as a hash, it's not clear what should the hash be encoded to. JSON, XML, or perhaps url params? Therefore WebMock expects you to stringify hash to expected encoding. I.e {a: b}.to_json

bblimke avatar Jan 30 '15 11:01 bblimke

(Sorry for the late response, and thanks for your reply.)

I see, that makes sense. Maybe this should be mentioned in the Readme?

Morred avatar Feb 12 '15 05:02 Morred

I am not sure that makes sense. If a server is actually returning JSON as the body content, the code parsing can assume its a Hash content type, therefore calling JSON.parse(response) will fail if its already a Hash. So its seems to me that Webmock should be able to send that as the response body, especially when a content-type of 'application/json' is explicitly passed.

joshuaswilcox avatar Mar 23 '15 14:03 joshuaswilcox

"If a server is actually returning JSON as the body content" - which server do you mean? We are returning a stubbed response. This response will be passed through specific http client library internals. You can't expect these libs to handle Hash.

WebMock would have to convert Hash declared in to_return to json string automatically first, but how would it know whether to convert this Hash to json or to xml?

bblimke avatar Mar 23 '15 14:03 bblimke

Would it not be best to use the headers content-type to determine whether to return JSON or XML? I am stubbing as so

stub_request(:post,'http://......').
     to_return(
      :status => 200,
      :body => '{"transaction_status":"declined"}',
      :headers => {"Content-Type"=> "application/json"})

but would prefer not to have to call JSON.parse(response_body) since that is not a step that is required in my code as JSON is implicitly declared. Maybe I am missing something though? FWIW, I am using Faraday to make the actual request.

joshuaswilcox avatar Mar 23 '15 15:03 joshuaswilcox

In case there is response content type declared it could work. Webmock would have to raise an error is case body is declared as a hash and headers are not declared.

bblimke avatar Mar 23 '15 15:03 bblimke

Most people will forget about headers.

bblimke avatar Mar 23 '15 15:03 bblimke

I don't think that is a problem to require a content-type when passing certain types of body content, since that is generally a good practice across http requests. Could i make a pull request for this functionality?

joshuaswilcox avatar Mar 24 '15 16:03 joshuaswilcox

I don't think it's a problem either, as long as user gets a clear error message explaining content type needs to be added if missing. Pull request would be most appreciated :)

bblimke avatar Mar 24 '15 21:03 bblimke

Great, thanks!

joshuaswilcox avatar Mar 25 '15 12:03 joshuaswilcox

I support and encourage implementation of this feature.

I'm surprised this isn't a more widespread issue. I've already encountered this issue on two separate occasions with two different web services. Most recently I needed to implement a work-around whereby I changed my response-parsing method to handle/convert the stubbed response strings when in reality outside of the test environment this extra handling would not be required.

s2t2 avatar Oct 31 '16 16:10 s2t2

I am running into this now. How is one supposed to mock a JSON response where the response is a hash? Not a JSON encoded string, but a hash. At the Ruby (Rails) layer the response is a hash in my real code, so it must also be in my test.

pboling avatar Feb 10 '18 04:02 pboling

@pboling

HTTP response body is never a hash (there is no Hash content type). Your ruby HTTP client most likely does the response body parsing for you and converts is to a Hash. In order for your http client to know that the response body is JSON, you need to indicate that by the Content-Type header.

stub_request(:get, 'http://......').
     to_return(
      body: json_string,
      headers: {"Content-Type"=> "application/json"})

bblimke avatar Feb 10 '18 10:02 bblimke

Thank you all for your answers, specially @joshuaswilcox and @bblimke. I was having issues with a spec for hours which mixed WebMock response with another gem expecting a hash. It wasn't until I ended up here that I was able to work through it. Had to use both JSON.generate and Content-Type suggestions.

I'm leaving my solution here:

stub_request(:get, /exampleurl.com/)
  .to_return(
     status: 200,
     body: JSON.generate(response_json),
     headers: {"Content-Type"=> "application/json"}
   )

dduqueti avatar Apr 13 '18 21:04 dduqueti

Thank you so much @bblimke @joshuaswilcox @dduqueti.

Assuming response is a hash and I'm writing a Rails app I can simplify @dduqueti's answer a little more using .to_json:

stub_request(:get, /exampleurl.com/)
  .to_return(
     status: 200,
     body: response.to_json,
     headers: {"Content-Type"=> "application/json"}
   )

brian-lutz-informed avatar Nov 07 '21 16:11 brian-lutz-informed

I think part of what makes this particularly confusing is that if you pass an empty hash, it doesn't throw an error.

jeffdill2 avatar Feb 23 '23 19:02 jeffdill2