Stubbing RestClient response in RSpec - ruby

I have the following spec...
describe "successful POST on /user/create" do
it "should redirect to dashboard" do
post '/user/create', {
:name => "dave",
:email => "dave#dave.com",
:password => "another_pass"
}
last_response.should be_redirect
follow_redirect!
last_request.url.should == 'http://example.org/dave/dashboard'
end
end
The post method on the Sinatra application makes a call to an external service using rest-client. I need to somehow stub the rest client call to send back canned responses so I don't have to invoke an actual HTTP call.
My application code is...
post '/user/create' do
user_name = params[:name]
response = RestClient.post('http://localhost:1885/api/users/', params.to_json, :content_type => :json, :accept => :json)
if response.code == 200
redirect to "/#{user_name}/dashboard"
else
raise response.to_s
end
end
Can someone tell me how I do this with RSpec? I've Googled around and come across many blog posts which scratch the surface but I can't actually find the answer. I'm pretty new to RSpec period.
Thanks

Using a mock for the response you can do this. I'm still pretty new to rspec and test in general, but this worked for me.
describe "successful POST on /user/create" do
it "should redirect to dashboard" do
RestClient = double
response = double
response.stub(:code) { 200 }
RestClient.stub(:post) { response }
post '/user/create', {
:name => "dave",
:email => "dave#dave.com",
:password => "another_pass"
}
last_response.should be_redirect
follow_redirect!
last_request.url.should == 'http://example.org/dave/dashboard'
end
end

Instance doubles are the way to go. If you stub a method that doesn't exist you get an error, which prevents you from calling an un-existing method in production code.
response = instance_double(RestClient::Response,
body: {
'isAvailable' => true,
'imageAvailable' => false,
}.to_json)
# or :get, :post, :etc
allow(RestClient::Request).to receive(:execute).and_return(response)

I would consider using a gem for a task like this.
Two of the most popular are WebMock and VCR.

Related

RSpec stub current_user in controller

Currently I am writing some controller test with rspec. The controller requires user to sign in before being usable.
if #current_user.nil?
do something
else
redirect
I currently have issues on how to stub the local variable current_user.
Why not have rspec login to your test system rather than stubbing current_user. You could do something like the following:
describe "Req #5 - login" do
it 'loads the login page' do
get '/login'
expect(last_response.status).to eq(200)
end
it 'loads the user index after login' do
user = User.create(:username => "Raptor", :password => "Raptor")
params = {
:username => "Raptor",
:password => "Raptor"
}
post '/login', params
follow_redirect!
expect(last_response.body).to include("Home Page for: Raptor")
end
end
You can stub the controller's current_user method like this:
let(:current_user) { User.create(...) }
before do
allow(#controller).to receive(:current_user).and_return(current_user)
end

What is the purpose of stubbing an HTTP request (e.g. using the WebMock gem)?

As a precursor FYI, I'm a budding developer. I'm trying to write a test for an http POST method for a Ruby gem. From what I can understand, when you stub an http response, for instance with the Ruby WebMock gem, you're basically telling it what to post and then artificially telling it what to respond with. For example, here is the code I'm trying to test:
## githubrepo.rb
module Githubrepo
include HTTParty
def self.create(attributes)
post = HTTParty.post(
'https://api.github.com/user/repos',
:headers => {
'User-Agent' => 'Githubrepo',
'Content-Type' => 'application/json',
'Accept' => 'application/json'
},
:basic_auth => {
:username => attributes[:username],
:password => attributes[:password]
},
:body => {
'name' => attributes[:repository],
'description' => attributes[:description]
}.to_json
)
Githubrepo.parse_response_from(post, attributes[:wants_ssh])
end
My RSpec test fails when I write:
Githubrepo.create(:repository => 'test', :username => 'test_user', :password => '1234')
because it makes a real HTTP request. It recommends I do the following instead:
stub_request(:post, "https://test_user:test_password#api.github.com/user/repos").
with(:body => "{\"name\":\"test_repo\",\"description\":null}",
:headers => {'Accept'=>'application/json', 'Content-Type'=>'application/json', 'User-Agent'=>'Githubrepo'}).
to_return(:status => 200, :body => "", :headers => {})
But to me, this seems like it's pointless since it's basically telling what to send and what to respond with. I can edit the URL to say "https://bananas#git-banana.banana" and the header to say Content-type => 'Rumplestilskin' and RSpec is ok with that. How am I supposed to integrate this into testing the functionality of the create method I specified above? Or if anything, can somebody point me to a solid beginner guide or blog to help me with this question? The Ruby gem READMEs seem to assume the user knows a thing or two already about this and I don't.
As Steve mentions in a comment, the point of this type of test is not to test the external API but instead that your code to handle and parse the response is correct.
As discussed in the comments to this question, check out the VCR gem for "recording" API responses to make sure your code processes them correctly: https://github.com/vcr/vcr

Why do I get a duplicated curb request?

I'm creating an application, which has authentication based on external API with login/register methods. I have a simple controller called RegistrationsController which fires a request using Curb.
This is the controller:
class RegistrationsController < ApplicationController
def new
end
def create
if params[:user][:email].present? && params[:user][:password].present? && params[:user][:phone].present? && params[:user][:login].present?
# API request
password = params[:user][:password]
body = {
"register" => {
"password" => password,
"email" => params[:user][:email],
"phone" => params[:user][:phone],
"login" => params[:user][:login]
}
}
c = Curl::Easy.http_post("http://domain.com/register", body.to_json
) do |curl|
curl.headers['Content-Type'] = 'application/json'
curl.headers['application'] = 'appname'
curl.headers['device'] = 'www'
end
c.perform
response_body = JSON.parse(c.body_str)
throw response_body # This line ALLWAYS gives me 'login taken' error
return
else
#user = User.new(params[:user])
render action: "new", notice: 'Error'
end
end
end
(I also have a views/registrations/new.html.slim view with a simple form but it's not important right now.)
My routes look like this:
match 'users/sign_up' => 'registrations#new', :via => :get, :as => :user_register
match 'users/sign_up' => 'registrations#create', :via => :post, :as => :user_create
My application, after I click the "Register" button on the registrations#new page, is triggering the Curb request two times. As a result, I'm always getting a 'login taken' error. The user is registered successfully but I'm not getting any result from the first request, just from the second one.
It's somehow caused by Rails and I'm 100% sure about it because it can be seen in the API server logs that the request is triggered twice. Also, I have exactly the same script written in PHP and, in there, the registration works fine.
In my Rails dev console, the request is triggered just one time so it's really strange.
Does anyone have any idea what is going on here?
I found the answer.
If anyone struggles with something similar, it was caused by the c.perform line. Just remove it and it will work fine.
I should study the docs better in the future.

400 Bad Request Nestful Ruby

I'm trying to use the Pocket API to authorize my application. So I'm using Nestful to send HTTP requests. And everytime I try sending a request I get a 400 Bad Request. The Pocket documentation says that it could be that it's either a missing consumer key or a missing redirect url.
But now I'm looking at the network tab in Chrome and it says that there is a 500 Internal Service Error. What are these things, and how can I fix them?
My code:
require "nestful"
require "sinatra"
require "uri"
get '/' do
params = {
:consumer_key => '******************************',
:redirect_uri => 'http://localhost:4567/callback'
}
response = Nestful.post 'https://www.getpocket.com/v3/oauth/request',
:params => params,
:format => :json
response.body
response.headers
end
get '/callback' do
"hello world"
end
So I got help on my problem. It turns out that params was already a hash, and so I did not need to say :params => params because that would be redundant.
Before
response = Nestful.post 'https://www.getpocket.com/v3/oauth/request',
:params => params,
:format => :json
After
response = Nestful.post 'https://getpocket.com/v3/oauth/request',
params,
:format => :json

Specifying Content Type in rspec

I'm trying to build an rspec test that sends JSON (or XML) via POST. However, I can't seem to actually get it working:
json = {.... data ....}.to_json
post '/model1.json',json,{'CONTENT_TYPE'=>'application/json'}
and this
json = {.... data ....}.to_json
post '/model1.json',json,{'Content-Type'=>'application/json'}
any ideas? THANKS!
In Rails 3, you can skip the header and #request.env stuff and just add a format parameter to your post call, e.g.:
post :create, format: :json, param: 'Value of Param'
There's a way to do this described in this thread -- it's a hack, but it seems to work:
#request.env["HTTP_ACCEPT"] = "application/json"
json = { ... data ... }.to_json
post :create, :some_param => json
A lot of frustration and variations and that's what worked for me.
Rails 3.2.12 Rspec 2.10
#request.env["HTTP_ACCEPT"] = "application/json"
#request.env["CONTENT_TYPE"] = "application/json"
put :update, :id => 1, "email" => "bing#test.com"
First of all, you don't want to test the built-in conversion of json to hash. Same applies to xml.
You test controller with data as hashes, not bothering wether it's json, xml or from a html form.
But if you would like to do that as an exercise, this is a standalone ruby script to do play with :)
require 'json'
url = URI.parse('http://localhost:3030/mymodels.json')
request = Net::HTTP::Post.new(url.path)
request.content_type="application/json"
request.basic_auth('username', 'password') #if used, else comment out
hash = {:mymodel => {:name => "Test Name 1", :description => "some data for testing description"}}
request.body = hash.to_json
response = Net::HTTP.start(url.host, url.port) {|http| http.request(request)}
puts response
to switch to xml, use content_type="text/xml" and
request.body = "<?xml version='1.0' encoding='UTF-8'?><somedata><name>Test Name 1</name><description>Some data for testing</description></somedata>"
A slightly more elegant test is to use the header helper:
header "HTTP_ACCEPT", "application/json"
json = {.... data ....}.to_json
post '/model1.json', json
Now this does exactly the same thing as setting #env; it's just a bit prettier.
The best way that I have found to test these things is with request tests. Request tests go through the full param parsing and routing stages of Rails. So I can write a test like this:
request_params = {:id => 1, :some_attribute => "some value"}
headers = {'Accept' => 'application/json', 'Content-Type' => 'application/json'}
put "/url/path", request_params.to_json, headers
expect(response).to be_success
I think that you can specify the headers with headers param:
post '/model1.json', headers: {'Content-type': 'application/json'}
Following the Rspec documentation of how provide JSON data.
#request.env["CONTENT_TYPE"] = "application/json"
OR pass in request
"CONTENT_TYPE" => "application/json"

Resources