Stub JSON.parse in RSPEC - ruby-on-rails

I'm doing some RSPEC testing here.
If i have this method:
JSON.parse("https://test.com/return_json/reviews.json")
Then I can stub it RSPEC like:
test_reviews = {"reviews" => [{"data1" => "1", "data2"=> "2"}]}
allow(JSON).to receive(:parse).and_return(test_reviews.to_json)
But for this kind (with other method inside (to_uri & read)).
JSON.parse("https://test.com/return_json/reviews.json".to_uri.read)
I tried to used receive_message_chain but no success.
Thanks in advance guys!

You code is not actually calling the url you. You need to make an http call and parse the body. I should probably look like this.
describe :ReviewsController
let(:uri) { URI('https://test.com/return_json/reviews.json') }
let(:reviews) { {"reviews" => [{"data1" => "1", "data2"=> "2"}]} }
before do
stub_request(:get, uri).
with(headers: {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}).
to_return(status: 200, body: JSON.dump(reviews), headers: {})
end
it 'does whatever you want' do
response = Net::HTTP.get(uri)
expect(JSON.parse(response.body)['data1']).to eq('1') # or whatever you want to test
end
end
It's better explained here.

Related

Mock GoogleAPI request

I am using the google-maps gem.
I am trying to mock/stub api requests unsuccessfully.
module GoogleMap
class Route
attr_accessor :start_location, :end_location
def initialize(start_location, end_location)
#start_location = start_location
#end_location = end_location
end
def driving_duration_in_seconds
route.duration.value
end
def driving_distance_in_meters
route.distance.value
end
def driving_distance_hash
return unless start_location && end_location
{ distance_in_meters: driving_distance_in_meters, duration_in_seconds: driving_duration_in_seconds }
end
private
def coordinates_as_strings(location)
"#{location.latitude},#{location.longitude}"
end
def route
#route ||= Google::Maps.route(coordinates_as_strings(start_location), coordinates_as_strings(end_location))
end
end
end
I need to stub:
WebMock::NetConnectNotAllowedError:
Real HTTP connections are disabled. Unregistered request: GET https://maps.googleapis.com/maps/api/directions/json?destination=37.19687112,116.43791248&key=<apikey>&language=en&origin=2.15362819,-81.63712649 with headers {'Accept'=>'*/*', 'Date'=>'Sat, 12 Feb 2022 21:35:55 GMT', 'User-Agent'=>'HTTPClient/1.0 (2.8.3, ruby 2.7.2 (2020-10-01))'}
You can stub this request with the following snippet:
stub_request(:get, "https://maps.googleapis.com/maps/api/directions/json?destination=37.19687112,116.43791248&key=<apikey>&language=en&origin=2.15362819,-81.63712649").
with(
headers: {
'Accept'=>'*/*',
'Date'=>'Sat, 12 Feb 2022 21:35:55 GMT',
'User-Agent'=>'HTTPClient/1.0 (2.8.3, ruby 2.7.2 (2020-10-01))'
}).
to_return(status: 200, body: "", headers: {})
If I try a most basic stub I get an error:
stub_request(:any, /maps.googleapis.com/).
to_return(status: 200, body: '', headers: {})
Google::Maps::InvalidResponseException:
unknown error: 783: unexpected token at ''
# .../gems/ruby-2.7.2/gems/google-maps-3.0.7/lib/google_maps/api.rb:64:in `rescue in response'
# .../gems/ruby-2.7.2/gems/google-maps-3.0.7/lib/google_maps/api.rb:60:in `response'
# .../.rvm/gems/ruby-2.7.2/gems/google-maps-3.0.7/lib/google_maps/api.rb:27:in `query'
I think it is erroring out because I am not passing a key in. But I don't see why I should have to pass in a valid api key into a webmock.
I also would not have my route defined by anything. And in order to test that route can return route.distance.value etc, I would need to mock with something.
For other tests I was successful in mocking instances, but to test this lib that it actually works, I feel like mocking instance methods and not that an api was actually called is a waste of a test. Maybe this is just a waste of time, and I should assume it works because I am using a gem.
But I was expecting something like this:
RSpec.describe GoogleMap do
let(:start_location) { create(:location) }
let(:end_location) { create(:location) }
context 'GoogleMaps::Route.new(start_location, end_location)' do
let(:subject) { GoogleMap::Route.new(start_location, end_location) }
# I have not been successful in stubbing this with the correct regex
# stub_request(:get, "https://maps.googleapis.com/maps/api/directions/json?destination=<lat>,<long>key=<key>&language=en&origin=<lat>,<long>").
# with(
# headers: {
# 'Accept'=>'*/*',
# 'Date'=>'Thu, 10 Feb 2022 21:09:02 GMT',
# 'User-Agent'=>'HTTPClient/1.0 (2.8.3, ruby 2.7.2 (2020-10-01))'
# }).
# to_return(status: 200, body: "", headers: {})
# stub_request(:get, %r{https:\/\/maps\.googleapis\.com\/maps\/api\/directions\/json\?destination=.+,.+&key=.+&language=en&origin=.+,.+}).
# stub_request(:any, /maps.googleapis.com/).
# to_return(status: 200, body: '', headers: {})
xit 'gives driving distance in seconds'
xit 'gives driving duration in meters'
end
end
Your WebMock is working fine. Google::Maps::InvalidResponseException is raised after WebMock has replaced the network call. At the point that exception is raised, the Google Maps API client is trying to parse what the network call returned, which is ''.
It's expecting some valid JSON to be returned. If you have your mock return {} is should get past that line. It may well stumble on some other exception later though, as the gem expects a certain schema.
You can dig that out and add in a valid response if you wanted to continue down this path. However, I'd recommend not mocking the network request as that's an implementation detail of a third party piece of code which could change at any time - making your test fail. Instead, I would mock out Google::Maps.route to return what you need it to.

Rspec request specs and Rails 5

I'm starting a new project, my first with Rails 5.1.0. I have a pb with my first request spec.
describe 'Users', type: :request do
it 'are created from external data' do
json_string = File.read('path/to/test_data/user_data.json')
params = { user: JSON.parse(json_string) }
headers = { "CONTENT_TYPE" => "application/json" }
expect do
post '/api/v1/users', params.to_s, headers
end.to change {
User.count
}.by(1)
expect(response.status).to eq 200
end
end
this spec return the error ArgumentError: wrong number of arguments (given 3, expected 1). The official documentation don't say much.
If I take out the .to_s, and send a hash, like this:
post '/api/v1/users', params, headers
I got another error:
ArgumentError: unknown keyword: user
Any thought?
I think they changed the syntax recently. Now it should use keyword args. So, something like this:
post '/api/v1/users', params: params, headers: headers
Here's a little addendum to Sergio's answer. If you are upgrading from Rails 4 to Rails 5, have lots of tests, and aren't too keen on changing them all – at least not until you've finished upgrading – I've found a way to make them work with the old method signature.
In my spec_helper I added
module FixLegacyTestRequests
def get(path, par = {}, hdr = {})
process(:get, path, params: par, headers: hdr)
end
def post(path, par = {}, hdr = {})
process(:post, path, params: par, headers: hdr)
end
def put(path, par = {}, hdr = {})
process(:put, path, params: par, headers: hdr)
end
def delete(path, par = {}, hdr = {})
process(:delete, path, params: par, headers: hdr)
end
end
and then I added this configuration for each test:
RSpec.configure do |config|
config.before :each do |example|
extend(FixLegacyTestRequests) # to be removed at some point!
end
end
My tests went back to working, and I think it should be safe because it's only applied to the currently running test and shouldn't pollute any gem's code such as with a monkey patch.

getting httparty undefined method `code' for #<Hash:0x007ff3625a4800> in rspec

I am writing specs for my first gem. But i am stuck with this weird error.
code for my rspec is
describe '#success' do
let(:resp) { {"TwilioResponse"=>{"SMSMessage"=>{"Sid"=>"0d1c0cbfb2b5e8f97dddb4479bdbbc6a", "AccountSid"=>"exotel_sid", "From"=>"/exotel_sid", "To"=>"1234", "DateCreated"=>"2016-07-18 15:35:29", "DateUpdated"=>"2016-07-18 15:35:29", "DateSent"=>nil, "Body"=>"test sms", "Direction"=>"outbound-api", "Uri"=>"/v1/Accounts/exotel_sid/Sms/Messages/0d1c0cbfb2b5e8f97dddb4479bdbbc6a", "ApiVersion"=>nil, "Price"=>nil, "Status"=>"queued"}}} }
before{ allow(Generator::Exotel).to receive(:post).with("/#{Generator::configuration.sid}/Sms/send",
{:body => {:To => 1234, :Body => 'test sms'},:basic_auth=>{:username=>"#{Generator::configuration.sid}", :password=>"#{Generator::configuration.token}"}}).and_return(resp) }
it 'returns response object' do
response = Generator::Exotel.send(:to => 1234, :body => "test sms")
expect(response).to eq ({"Status"=>200, "Message"=>"Success"})
end
end
when i run rspec i am getting this error
NoMethodError:
undefined method `code' for #<Hash:0x007ff3625a4800>
This is where my response.code is being called
def handle_response(response)
response_base = response['TwilioResponse']
if response_base.include?('RestException')
response_base['RestException']
else
{"Status" => response.code, "Message" => "Success" }
end
end
I know httparty creates a response object for request and returns response code. But i am not getting how do i create a dummy response_code
so that my test case pass. It's nearly 2 days since i am stuck here. Anyone help please. I am really new to ruby and for first time writing spec. Any help will be appreciated.
Update - result for response.inspect
> Generator::Exotel.send(:to => 9030435595, :body => 'jsdhgjkdfg')
it returns following response
> #<HTTParty::Response:0x7fb8c02f93d0 parsed_response={"TwilioResponse"=>{"SMSMessage"=>{"Sid"=>"d6ee0650072c82941ad2f06746d14ab4", "AccountSid"=>"sinscary", "From"=>"/sinscary", "To"=>"9030435595", "DateCreated"=>"2016-07-21 19:56:07", "DateUpdated"=>"2016-07-21 19:56:07", "DateSent"=>nil, "Body"=>"jsdhgjkdfg", "Direction"=>"outbound-api", "Uri"=>"/v1/Accounts/sinscary/Sms/Messages/d6ee0650072c82941ad2f06746d14ab4", "ApiVersion"=>nil, "Price"=>nil, "Status"=>"queued"}}}, #response=#<Net::HTTPOK 200 OK readbody=true>, #headers={"content-type"=>["application/xml"], "date"=>["Thu, 21 Jul 2016 14:26:07 GMT"], "server"=>["Apache/2.2.29 (Amazon)"], "x-powered-by"=>["PHP/5.3.28"], "content-length"=>["542"], "connection"=>["Close"]}>
OK, you are mocking an HTTParty::Response. One way would be to mock it directly with only code and parsed_response:
let(:resp) do
Struct.new(:code, :parsed_response).new(200, {"TwilioResponse"=>...})
end
Another way would be to instantiate a real HTTParty::Response with:
let(:resp) do
HTTParty::Response.new(
nil,
Struct.new(:code, :parsed_response)
.new(200, {"TwilioResponse"=>...}), -> { ... }
)
end
I would go with the first approach. Please note, that you probably will need to change in handle_response:
response_base = response['TwilioResponse']
to
response_base = response.parsed_response['TwilioResponse']

How to stub requests on domain or after method?

config.before(:each) do
stub_request(:post, "https://api.3rdpartysmsprovider.com/send.php?body=This%20is%20a%20test%20message&destination=60123456789&dlr='1'&output=json&password=0000000&reference=#{#text.sms_uid}&sender=silver&username=0000000").
to_return(:status => 200, :body => "01", :headers => {})
end
I am currently writing specs for a service class that sends an SMS and creates a log of it in our database. I'm trying to stub this request, however #text.sms_uid is a SecureRandom.urlsafe_base64 random code. Also I'm stubbing in config.before(:each).
Because of that, I can't specify the sms_uid in stub_request as the random sms_uid is generated after the stub is called. This causes the test to fail every time. Is there a way I can stub the request after it generates the code (in other words, after it goes through the specific method) or is there a way to stub all requests going through the domain "https://api.silverstreet.com"?
I see two options:
Stub SecureRandom.urlsafe_base64 to return a known string and use that known string when you stub_request:
config.before(:each) do
known_string = "known-string"
allow(SecureRandom).to receive(:known_string) { known_string }
stub_request(:post, "https://api.3rdpartysmsprovider.com/send.php?body=This%20is%20a%20test%20message&destination=60123456789&dlr='1'&output=json&password=0000000&reference=#{known_string}&sender=silver&username=0000000").
to_return(status: 200, body: "01", headers: {})
end
If SecureRandom.urlsafe_base64 is used in other places in your application, you'll need to stub it only in the specs where this request is generated.
Yes, you can stub any POST to that hostname
stub_request(:post, "api.3rdpartysmsprovider.com").
to_return(status: 200, body: "01", headers: {})
or even any request of any kind to that hostname
stub_request(:any, "api.3rdpartysmsprovider.com").
to_return(status: 200, body: "01", headers: {})
and webmock has a very large number of other ways to match requests.

Test oauth2 get_token with rspec

I'm creating a controller spec for the get_token part of an oauth2 authentication. At this point the user has authorized my app and I need to generate and save the token and other information. Rspec fails with a somewhat cryptic error.
Failure/Error: get :auth, { code: "auth_code", scope: "read_write" }
OAuth2::Error:
{:token_type=>"bearer",
:stripe_publishable_key=>"PUBLISHABLE_KEY",
:scope=>"read_write",
:livemode=>"false",
:stripe_user_id=>"USER_ID",
:refresh_token=>"REFRESH_TOKEN",
:access_token=>"ACCESS_TOKEN"}
Here's the controller code. Rspec says it fails on get_token.
require 'oauth2'
def auth
code = params[:code]
client = oauth_client
token_response = client.auth_code.get_token(code, params: { scope: 'read_write' })
token = token_response.token
And here's the test. The webmock should be intercepting get_token. It is the autogenerated webmock suggeted by rspec that I filled in the body with the appropriate request and response body.
before do
stub_request(:post, "https://connect.stripe.com/oauth/token").
with(:body => {"client_id"=>"CLIENT_ID",
"client_secret"=>"SOME_SECRET",
"code"=>"auth_code",
"grant_type"=>"authorization_code",
"params"=>{"scope"=>"read_write"}},
:headers => {'Accept'=>'*/*',
'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
'Content-Type'=>'application/x-www-form-urlencoded',
'User-Agent'=>'Faraday v0.9.0'}).
to_return(:status => 200,
:body => {token_type: "bearer",
stripe_publishable_key: "PUBLISHABLE_KEY",
scope: "read_write",
livemode: "false",
stripe_user_id: "USER_ID",
refresh_token: "REFRESH_TOKEN",
access_token: "ACCESS_TOKEN"},
:headers => {})
end
describe "#auth" do
it "creates a payment gateway" do
get :auth, { code: "auth_code", scope: "read_write"
end
end
This process already works in practice so at least the controller code is not to blame. What am I doing wrong?
After work hard having the same problem I have a solution. Is the header returned in the stub request, you have it blank. The oauth2 gem use the Content-Type to define how to parse the body. Try the stub putting the header in this way:
stub_request(:post, "https://connect.stripe.com/oauth/token").
with(:body => {"client_id"=>"CLIENT_ID",
"client_secret"=>"SOME_SECRET",
"code"=>"auth_code",
"grant_type"=>"authorization_code",
"params"=>{"scope"=>"read_write"}},
:headers => {'Accept'=>'*/*',
'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
'Content-Type'=>'application/x-www-form-urlencoded',
'User-Agent'=>'Faraday v0.9.0'}).
to_return(:status => 200,
:body => {token_type: "bearer",
stripe_publishable_key: "PUBLISHABLE_KEY",
scope: "read_write",
livemode: "false",
stripe_user_id: "USER_ID",
refresh_token: "REFRESH_TOKEN",
access_token: "ACCESS_TOKEN"},
:headers => { 'Content-Type'=> 'application/json;charset=UTF-8'})
I think you're getting this error, because you've stubbed only part of oauth session, i.e. you're trying to send a stale token (or something like that) that has been provided by webmock.
Instead of manual stubbing of these requests, I'd suggest to use special tool: stripe-ruby-mock gem, which has been designed exactly for testing Stripe API.
As an alternative, you may use VCR gem (here are the docs), which allows to write on disk all your http session and play it as it was live. Great tool, highly recommended.

Resources