I am trying to write a controller spec to test that the right partial is rendering after a post request.
Here is the controller method being posted to:
def lookup
#guest = Guest.where("mobile_number = ?", params[:lookup_mobile_phone_number]).first_or_initialize do |g|
g.mobile_number = params[:lookup_mobile_phone_number]
end
if #guest.new_record?
#visit = Visit.new(hotel_id: params[:hotel_id])
render partial: "guests/form"
else
#visit = Visit.new(guest_id: #guest.id, hotel_id: params[:hotel_id])
render partial: "visits/form"
end
end
Here is the spec/controllers/guests_controller_spec.rb I wrote that is failing:
RSpec.describe GuestsController, :type => :controller do
describe "#lookup" do
render_views
let!(:returning_guest) { create(:test_guest) }
context "when guest is already registered with hotel" do
it "renders visits/form" do
post :lookup, :guest => { :lookup_mobile_phone_number => "5553331212"}
expect(response).to render_template(:partial => 'visits/form')
end
end
end
end
Here is the factory I'm using for :test_guest
FactoryGirl.define do
factory :test_guest, :class => 'Guest' do
name 'Jack Guest'
mobile_number '5553331212'
end
end
This is the response I am getting when the test fails
1) GuestsController#lookup when guest is already registered with hotel renders visits/form
Failure/Error: expect(response).to render_template(:partial => 'visits/form')
expecting partial <visits/form> but action rendered <["shared/_hotel_agent_name", "_hotel_agent_name", "guests/_form", "_form"]>.
Expected {"shared/_hotel_agent_name"=>1, "_hotel_agent_name"=>1, "guests/_form"=>1, "_form"=>1} to include "visits/form".
# ./spec/controllers/guests_controller_spec.rb:16:in `block (4 levels) in <top (required)>'
I've been hacking away at this a for a few days now, trying different approaches found on here with no luck. Any help would be much appreciated :)
You send
post :lookup, :guest => { :lookup_mobile_phone_number => "5553331212"}
but in controller, you use
params[:lookup_mobile_phone_number]
not
params[:guest][:lookup_mobile_phone_number]
So to fix it, according to your controller, do
post :lookup, :lookup_mobile_phone_number => "5553331212"
Related
I'm using rspec and Factory Girl for testing. When testing the POST #create section of my posts_controller I'm getting the error in the title.
Failures:
1) PostsController POST #create with valid attributes redirects to the post
Failure/Error: response.should redirect_to Post.last
Expected response to be a <redirect>, but was <200>
# ./spec/controllers/posts_controller_spec.rb:59:in `block (4 levels) in <top (required)>'
This is the code from the spec being tested. I'm sure it's not the most efficient way of doing this, but it does work.
def create
#post = Post.new(
:text => post_params[:text],
:embed => post_params[:embed],
:user => current_user,
:title => post_params[:title],
:tag_list => post_params[:tag_list],
:cagegory_ids => post_params[:category_ids]
)
if #post.save
redirect_to #post
else
render 'new'
end
end
...
private
def post_params
params.require(:post).permit(:title, :text, :embed, :user_id, :tag_list,
:category_ids => [])
end
Here's the factory.
FactoryGirl.define do
factory :post do
title { Faker::Lorem.characters(char_count = 20) }
text { Faker::Lorem.characters(char_count = 150) }
user
categories {
Array(5..10).sample.times.map do
FactoryGirl.create(:category)
end
}
end
end
And the relevant part of the spec
describe "POST #create" do
context "with valid attributes" do
it "saves the new post" do
expect{
post :create, post: FactoryGirl.create(:post).attributes
}.to change(Post,:count).by(1)
end
it "redirects to the post" do
post :create, post: FactoryGirl.create(:post).attributes
response.should redirect_to Post.last
end
end
end
The other test, "saves the new post," works fine. I've tried other variations of the redirect_to line, such as "redirect_to(posts_path(assigns[:post])) and it throws the same error.
Any ideas?
OK, I fixed my problem. It isn't pretty but it works.
The issue is with Factory Girl and associations, and I'm definitely not the first to have that problem, but none of the other solutions worked.
I ended up adding this before :each at the top of the POST #create section...
describe "POST #create" do
before :each do
Post.destroy_all
#cat = FactoryGirl.create(:category)
#newpost = FactoryGirl.build(:post)
#post_params = {category_ids: [#cat.id]}.merge(#newpost.attributes)
end
...
...to put the post params into a new hash that I could call later on in the code like this...
it "saves the new post" do
expect{
post :create, post: #post_params
}.to change(Post,:count).by(1)
end
it "redirects to the post" do
post :create, post: #post_params
response.should redirect_to Post.last
end
So this is solved. It adds a bit of overhead to the test but it works. I won't mark this as THE solution for a couple of days in case someone else comes around with some better, simpler code. I definitely welcome any more ideas.
I assume the problem is in factory. Most likely post instance didn't pass validations and controller renders new view, which is not redirect, but success 200. Add logger in the controller and take a look if record actually saves. You can also read through test log tail -f log/test.log.
I'm trying to test an existing rails project with rspec. And I want to test a controller but getting an error which I can't solve :S
Here is the my spec code ;
require 'spec_helper'
describe BriefNotesController do
before(:all) do
#customer=Factory(:customer)
#project=Factory(:project_started, :owner => #customer)
end
context 'get :new' do
it 'should redirect to login page for not signed in users' do
get :new, :project_id => #project.id
response.should redirect_to("/kullanici-girisi")
end
it 'should be success and render new brief note page for project owner' do
sign_in #customer
get :new, :project_id => #project.id
response.should be_success
end
end
end
Here is the my controller code ;
class BriefNotesController < ApplicationController
before_filter :authenticate_user!
before_filter :find_project
def new
#brief_note = #project.brief_notes.new
end
def create
#brief_note = #project.brief_notes.build(params[:brief_note])
if #brief_note.save
redirect_to brief_project_path(#project)
else
render :action => :new
end
end
private
def find_project
#project = current_user.projects.find_by_cached_slug([params[:project_id]])
end
end
I think current_user.projects.find_by_cached_slug method don't work. So this is the error;
Failures:
1) BriefNotesController get :new should be success and render new brief note page for project owner
Failure/Error: get :new, :project_id => #project.id
NoMethodError:
undefined method `brief_notes' for nil:NilClass
# ./app/controllers/brief_notes_controller.rb:6:in `new'
# ./spec/controllers/brief_notes_controller_spec.rb:19:in `block (3 levels) in <top (required)>'
I can't say for certain without more information about your models, but a likely culprit is that you're passing #project.id as the request parameter, but you're doing the lookup by cached_slug. Try #project.to_param instead.
The error is coming from your find_project filter: find_by_cached_slug is returning a nil, which is assigned to #project, and that triggers the undefined method error when brief_notes is called on it (in the new action).
From your spec description I assume that it shouldn't even be executing the new code, and instead redirecting in authenticate_user!? I don't use devise myself (this is a devise method, right?) so I'm not sure of the specifics of that method, but I think that's where your problem is coming from.
I don't think the problem is your FactoryGirl syntax, which is deprecated but should still work.
I need to know how to test this controller action
def create_mobile
if mobile_user = MobileUser.authenticate(params[:email], params[:password])
session[:mobile_user_id] = mobile_user.id
respond_to do |format|
format.json { head :ok }
end
else
respond_to do |format|
format.json { head :unauthorised }
end
end
end
The route is a post request to sessions/create_mobile and as you can see the action only responds to json
My current controller spec looks like this
describe SessionsController, "Logging in" do
before(:each) do
#mobile_user = FactoryGirl.create(:valid_mobile_user)
end
it "Should log in mobile user" do
#request.env["HTTP_ACCEPT"] = "application/json"
post :create_mobile, {:password => #mobile_user.password, :email => #mobile_user.email}
response.should be_success
end
it "should fail log in mobile user" do
#request.env["HTTP_ACCEPT"] = "application/json"
post :create_mobile, {:password => 'joe', :email => #mobile_user.email}
response.should_not be_success
end
end
The test results in
1) SessionsController Logging in should log in mobile user
Failure/Error: response.should be_success
expected success? to return true, got false
# ./spec/controllers/sessions_controller_spec.rb:11:in `block (2 levels) in <top (required)>'
So there is either a problem with my application code or there is a problem with my test code but either way there is no problem with the authenticate method as the MobileUser model spec passes which looks like this
it "should authenticate" do
mu = FactoryGirl.create(:valid_mobile_user)
assert_equal 1, MobileUser.count
assert_equal mu, MobileUser.authenticate(mu.email, mu.password)
end
Any help in sorting this out greatly appreciated.
Thanks in advance
UPDATE
As suggested below, using
post :create_mobile, {:password => 'joe', :email => #mobile_user.email} :format => :json
or using
#request.env["HTTP_ACCEPT"] = "application/json"
or a combination of both makes no difference
UPDATE 2
The test has just started working for no reason that I can fathom (other than I never understood why it wasn't working in the first place).
Totally strange!
I met the same issue recently. below is my test code
post :create, :login => 'mock_user', :password => 'passwd', :format => :json
expected = {
:login => 'mock_user'
}.to_json
session[:user_id].should == mock_user.id
response.body.should == expected
The test result is false, caused by response.body is blank. I checked the test.log, and found got 406 unacceptable request error in controller. After googled a lot, I changed to :format => 'json'. Then I got the expected test result.
Since you're trying to post JSON to the controller, shouldn't you convert your hash to JSON?
post :create_mobile, {:password => 'joe', :email => #mobile_user.email}.to_json
If you don't want to add seven characters to your code just to see if it works, then you should output your params to logger to see what they look like.
def create_mobile
logger.info "LOGIN PARAMS U/N: #{params[:email]}, P/W: #{params[:password]}"
...
Then tail -f log/test.log to see what your controller looks like during your test.
have you tried adding a :format option to the post statement
Here is the rspec code for testing show in customers controller:
it "'show' should be successful" do
#category = Factory(:category)
#sales = Factory(:user)
#customer = Factory(:customer, :category1_id => category.id, :sales_id => sales.id)
category = mock_model('Category')
sales = mock_model('User')
customer = mock_model(Category, :sales_id => sales.id, :category1_id => category.id)
get 'show' , :id => customer.id
response.should be_success
end
Here is the error in rspec:
CustomersController GET customer page 'show' should be successful
Failure/Error: get 'show' , :id => customer.id
ActiveRecord::RecordNotFound:
Couldn't find Customer with id=1003
# c:in `find'
# ./app/controllers/customers_controller.rb:59:in `show'
# ./spec/controllers/customers_controller_spec.rb:50:in `block (3 levels) in <top (required)>'
The rspec test passes with the real record created by Factory (see #ed in rspec code)
What's wrong with the mock? Thanks.
The spec is failing inside the controller's action which doesn't know anything about your mocks unless you told it explicitly.
Add this to your spec, before the get statement.
Customer.should_receive(:find).and_return(customer)
I`m trying to test my controller with rspec and always get an error.
users_controller.rb:
def update
#user.update_attributes!(params[:user])
redirect_to #user, :status => 202, :text => render_to_string(:partial => "users/show", :type => "json", :locals => {:user => #user})
#notice, that redirect_to was reinitialized and :text is a parameter for response_body
end
_show.tokamak
user {
id user.id
email user.email
username user.username
}
spec file
it "should NOT update user username" do
username = #user.username
put 'update', :id => #user.id, :user => {:username => username+"abc"}, :format => :json
response.status.should be(202)
response.headers["Location"].length.should be > 0
puts response.body
#user.reload
#user.username.should eq(username)
end
end
So I get an error:
Failure/Error: put 'update', :id =>
#user.id, :user => {:username =>
username+"abc"}, :format => :json
ActionView::Template::Error:
You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]
# C:/Users/makaroni4/free_frog/ffapi/app/views/users/_show.tokamak:1:in
_app_views_users__show_tokamak___509498818
_32151168_368311673'
# C:/Users/makaroni4/XXX/XXX/app/controllers/users_controller.rb:22:in
update'
# ./users_controller_spec.rb:34:in
`block (4 levels) in '
So may be I call render_to_string method wrong?
Try stubbing out find?
mock_user = User.stub(:find).with(#user.id) {#user}
To be honest I'd go a few steps further and make sure you mock and stub most of the relevant behavior of the User object (or whatever class #user is). Keep in mind you're only testing that the controller action returns what you expect if you give it valid input--not that the model itself does the right thing.
I had a lot of difficulty wrapping my head around the differences in model vs. controller specs...
I hope this helps...if not, I apologize in advance...
EDIT:
I'll take this a step futher and suggest this test is actually a model test. The actual controller test would be something like as the way your spec test should behave:
it "should NOT update user with invalid input" do
mock_user = mock_model(User, {}).as_null_object
User.stub(:find).with("12") {mock_user}
User.stub(:update_attributes).with({}).and_return(false)
put 'update', :id => "12"
# test that your output is correct, or even if the render target is what you expect.
end