How do you test a controller in Rspec if the controller only responds with javascript? For example this would be my actual code:
some view.html.erb
link_to 'More Chips', add_chips_path, :remote => true
chips_controller
def add_chips
Chips.create(:color => :red)
#chips = Chips.all
end
add_chips.js.erb
$('#chip_count').html('<%=j render("chips/list", :chips => #chips) %>');
Rspec Test
spec/controllers/chips_controller_spec
it "should add chips" do
post :add_chips, :format => 'js'
end
When I try to post to this using RSpec I get a Missing Template error because it is sending an HTML request but there isn't an HTML view. I've tried passing in a format but that doesn't seem to work. I know I can put in a "dummy" html view to make it pass but that seems like a hack.
Thanks
If you're trying to perform an Ajax request from a controller spec, instead of doing post :add_chips and passing the format as a parameter, you should do:
xhr :post, :add_chips, #params
...without format in params. That will do a JavaScript request and respond with the .js template.
Update:
In recent versions of Rails/RSpec, you should instead do:
post :add_chips, params: #params, xhr: true
Ok, I figured out what I was doing wrong. I was passing a parameters hash in with my test and the format needs to be included in the same hash. so instead of this:
#params = {:chip_color => 'red'}
...
post :add_chips, #params, :format => 'js'
It needs to look like this
#params = {:chip_color => 'red', :format => 'js'}
...
post :add_chips, #params
Related
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
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.
I'm converting our Rails 3 web app to use jQuery mobile, and I'm having problems with "remote" links.
I have the following link:
= link_to "Text", foo_url, :method => :put, :remote => true
Which, on the server, I'm handling like this:
respond_to do |format|
if foo.save
format.html { redirect_back_or_to blah_url }
format.json { render :json => {:status => "ok"} }
end
end
This used to work wonderfully. However, since I've added jQuery Mobile, the controller code goes through the "html" branch instead of the "json" one, and responds with a redirect.
I've tried adding
:data => { :ajax => "false" }
to the link, but I get the same effect.
Before jQuery Mobile, UJS was sending the request with the following accept header:
Accept:application/json, text/javascript, */*; q=0.01
while with jQuery Mobile, I'm getting this header:
Accept:*/*;q=0.5, text/javascript, application/javascript, application/ecmascript, application/x-ecmascript
I believe this change in headers is the culprit of the change in server-side behaviour. I haven't been able to debug through the client side to figure out who's doing what exactly. UJS is clearly still doing something, since I'm getting a "PUT request" of sorts, things get routed appropriately, etc, but I'm not sure what's changing the headers.
Thank you!
Daniel
By default remote: true goes to the format.js clause (and searches for some .js.erb template to send back), and defaults to format.html and sends back the html template.
You should use ”data-type” => :json in your link_to call if you want to return json, like:
<%= link_to 'Show Full Article', #article, :remote => true, "data-type" => :json %>
Source: http://tech.thereq.com/post/17243732577/rails-3-using-link-to-remote-true-with-jquery-ujs
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.
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"