How to correctly replicate response body of an Octokit requests' response for webmock stub
Asked Answered
D

4

6

Octokit responses are of type Sawyer::Response

They look like this:

{:name=>"code.py",
:content => "some content"}

I am trying to stub my request like so

reponse_body = {:content => "some content"}
stub_request(:any, /.*api.github.com\/repos\/my_repo\/(.*)\/code.py/).to_return(:status => 200, :body => response_body)

In my code I then call response.content, so I want to be able to get the content from the response.

I currently get the error: 'WebMock::Response::InvalidBody: must be one of: [Proc, IO, Pathname, String, Array]. 'Hash' given'. What is the proper format for response_body? If I turn it into a json, I then can't do response.content on the object in my code.

Domiciliate answered 3/6, 2016 at 19:9 Comment(0)
C
3

You are passing a hash as the expected response and Webmock doesn’t know what that should be encoded to (see this Webmock issue). As you mentioned, you could use response_body.to_json, however you would then be unable to use the dot notation to access data.

Since you’re using RSpec, I’d make use of Test Doubles to pretend you have a Sawyer::Resource object:

response_body = 
  [
    double("Sawyer::Resource",
      {
        :name=>"code.py",
        :content => "some content"
      })
  ]

You should then be able to access data using the dot notation like you would with the actual response.

Clyte answered 15/6, 2016 at 11:16 Comment(5)
Nice idea, I thought so too, but get same error on the double. WebMock::Response::InvalidBody: must be one of: [Proc, IO, Pathname, String, Array].'RSpec::Mocks::Double' givenSports
Gah! I made a typo. The response_body should be an array with one element. That element would then be the test double. I'll update my answer.Clyte
Thats all very well and will work, but now the code under test we receive an array, when it would normally receive a hash. In fact, the double is not required if the hash is returned within the array, as that is an allowed return type.Sports
This leads to error if I use response.content in code: NoMethodError: undefined method `content' for [#<Double "Sawyer::Resource">]:ArrayArchangel
If you're not expecting a response body that's an array you can remove those square brackets. response_body = double("Sawyer::Resource", { :name=>"code.py", :content => "some content"})Clyte
P
3

You need to provide the JSON body as a string, and an appropriate Content-Type header. E.g., to stub the call

Octokit::Client.new.user(user_login)

you need something like

stub_request(:get, "https://api.github.com/users/#{user_login}")
  .to_return(
    status: 200,
    body: user_json, # a string containing the JSON data
    headers: { content_type: 'application/json; charset=utf-8' }
  )

(If you don't provide the Content-Type header, Octokit won't try to parse the JSON and you'll just get the raw string back.)

If you look at the Octokit source you can see how they use Webmock in their own tests. (The json_response() method called in that test is in helper.rb.)

Pomfrey answered 29/3, 2019 at 17:33 Comment(2)
The thing I was missing was the response header, thanks!Gatha
I will just add that user_json = "{\"content\": \"some content\"}" for above question.Archangel
S
2

I had this exact problem, and in the end solved it by stubbing out the Octokit client. In order to check the test coverage within Octokit, I followed instructions here.

Octokit requests are all tested with VCR, so assuming you are happy with their test coverage, it is reasonably safe to stub Octokit::Client within your application.

Staphylococcus answered 3/3, 2017 at 15:12 Comment(0)
A
0

In case if someone is still confused, here is a complete sample how Octokit call could be tested with rspec.

Method:

require 'octokit'

def get_user_name(login)
  Octokit.configure { |c|  c.api_endpoint = 'https://git.my_company.com/api/v3/' }
  client = Octokit::Client.new(:access_token => 'my_token')
  response = client.user(login)
  return response.name
end

Test:

describe '#get_user_name' do
  it 'should return name' do
    response_body = {:name => "James Bond"}
    stub_request(:get, "https://git.my_company.com/api/v3/users/bondj").
      to_return(status: 200,
                body: JSON.generate(response_body),
                headers: { content_type: 'application/json; charset=utf-8' })

    result = subject.send(:get_user_name, 'bondj')
    expect(result).to eq('James Bond')
  end
end
Archangel answered 1/8, 2022 at 9:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.