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.

4 Answers4

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.

Andy Stabler
  • 1,309
  • 1
  • 15
  • 19
  • 1
    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' given` – Ian Vaughan Sep 10 '16 at 10:31
  • 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. – Andy Stabler Sep 10 '16 at 14:57
  • 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. – Ian Vaughan Sep 11 '16 at 15:35
  • This leads to error if I use response.content in code: NoMethodError: undefined method `content' for [#]:Array – jing Aug 01 '22 at 07:45
  • 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"})` – Andy Stabler Aug 01 '22 at 13:41
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.)

David Moles
  • 48,006
  • 27
  • 136
  • 235
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.

ddosque
  • 56
  • 1
  • 5
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
jing
  • 1,919
  • 2
  • 20
  • 39