Good question!
Disclaimer: I'm still learning about RoR, but I can try to answer this question as best as I can. If anyone has suggestions about how I can improve my response, please let me know!
Testing a class method (using stubs)
So, in order to test your method called "pullrequestreview", you'll need to stub a method within the Github::Client class. I personally have not worked with GitHub's API, so let's assume that it is a static class with a few class methods (including pull_request_reviews).
(Note: I renamed "pullrequestreview" to pull_request_review, because it is more idiomatic in Ruby)
In the example below, I stubbed Github::Client.pull_request_reviews, so that it will return res_data whenever it is called:
Github::Client.stubs(:pull_request_reviews).returns(res_data)
Next, I use a "factory" to generate a github_client argument to pass into code_review_sign_off.pull_request_review. Since the GitHub method is stubbed, it doesn't really matter what I pass into it. In more complex test scenarios, it might be a good idea to make use of a factory bot gem to build out different GitHub related objects (with different trait options). For example, using a factory, you could define a new github_client with a "pull_request" trait like so:
github_client = create(:github_client, :with_pull_request)
In your post, code_review_sign_off.pull_request_review is expecting an iterable object from GitHub. Let's assume that code_review_sign_off.pull_request_review returns the number of elements within the array. Since I have control over GitHub's response, I know that only one element will be returned, so the result should be 1, which I assert using:
assert_equal pull_request_review_response, 1
So, in general when testing, you want to issolate the function that you're interested in and use stubs to keep everything else constant.
require 'test_helper'
class CodeReviewSignedOffTest < ActionDispatch::IntegrationTest
test 'with CodeReviewSignedOff method' do
res_data = [{
:id => 1,
:data => "some_data"
}]
Github::Client.stubs(:pull_request_reviews).returns(res_data)
github_client = create(:github_client, :with_pull_request)
code_review_sign_off = CodeReviewSignedOff.new
pull_request_review_response = code_review_sign_off.pull_request_review(github_client)
...
assert_equal pull_request_review_response, 1
end
end
(Note: I'm using the mocha/minitest in the examples)
Bonus: Mocking an API endpoint
So, first, I am going to assume you have your test environment for RSpec properly configured (you ran rails generate rspec:install command) and you have the appropriate /spec directory.
The first step to mocking an API endpoint is to stub the calls to the actual endpoints by using a gem like WebMock. So, install the gem and add require 'webmock/rspec' to /spec/spec_helper.rb
Within /spec/spec_helper.rb you'll need to define a few things,
First, add the following to ensure that you're not continuing to make real calls to the API:
WebMock.disable_net_connect!(allow_localhost: true)
Next, you'll need a stub for each API endpoint that you're mocking. For example, in your case, you'd need to define something like:
RSpec.configure do |config|
config.before(:each) do
pull_request_review_response = [
{
"id": 80,
"node_id": "MDE3OlB1bGxSZXF1ZXN0UmV2aWV3ODA=",
"user": {
"login": "octocat",
"id": 1,
"node_id": "MDQ6VXNlcjE=",
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"gravatar_id": "",
"url": "https://api.github.com/users/octocat",
"html_url": "https://github.com/octocat",
"followers_url": "https://api.github.com/users/octocat/followers",
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
"organizations_url": "https://api.github.com/users/octocat/orgs",
"repos_url": "https://api.github.com/users/octocat/repos",
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
"received_events_url": "https://api.github.com/users/octocat/received_events",
"type": "User",
"site_admin": false
},
"body": "Here is the body for the review.",
"state": "APPROVED",
"html_url": "https://github.com/octocat/Hello-World/pull/12#pullrequestreview-80",
"pull_request_url": "https://api.github.com/repos/octocat/Hello-World/pulls/12",
"_links": {
"html": {
"href": "https://github.com/octocat/Hello-World/pull/12#pullrequestreview-80"
},
"pull_request": {
"href": "https://api.github.com/repos/octocat/Hello-World/pulls/12"
}
},
"submitted_at": "2019-11-17T17:43:43Z",
"commit_id": "ecdd80bb57125d7ba9641ffaa4d7d2c19d3f3091",
"author_association": "COLLABORATOR"
}
]
owner = 'octocat',
repo = 'hello-world',
pull_number = 42
stub_request(:get, "https://api.github.com/repos/#{owner}/#{repo}/pulls/#{pull_number}/reviews").
to_return(status: 200, body: pull_request_review_response.to_json,
headers: {'Accept'=>'*/*', 'User-Agent'=>'Ruby'})
end
# ....
end
The important part of this code is stub_request which takes an HTTP request method, a URL path, and it returns the arguments passed into to_return (as an HTTP response). The response object will include a status, body, and a header.
I used the examples from the WebMock README and the API doc from GitHub to produce the example above (Note: It is untested).
Now, you can define a new test that calls the Mock endpoint:
require 'spec_helper'
class CodeReviewSignedOffTest < ActionDispatch::IntegrationTest
test 'GET pull_request_review_response endpoint from GitHub mock' do
uri = URI('https://api.github.com/repos/octocat/hello-world/pulls/42/reviews')
response = Net::HTTP.get(uri)
data = JSON.parse(response&.body)
...
end
end
Hopefully you'll see the data you defined within your mock API come through. I haven't tested this code, so it might require some debugging.
Let me know if you have any questions or run into trouble writing your tests! Good luck.