RSpec Newbie: Devise/Cancan causing otherwise working controller spec to fail - ruby-on-rails

I'm trying to get an RSpec controller spec to pass. It's almost identical to the scaffold-generated spec, except a user is signed into devise first. If I disable 'load_and_authorize_resource' from the controller (which checks permissions), everything works fine. But if I put the line back in, it fails with:
1) PostsController logged in administrator POST create with valid params assigns a newly created post as #post
Failure/Error: post :create, :post => {'title' => 'test title'}
<Post(id: integer, title: string, cached_slug: string, content: text, user_id: integer, created_at: datetime, updated_at: datetime) (class)> received :new with unexpected arguments
expected: ({"title"=>"test title"})
got: (no args)
# ./spec/controllers/posts_controller_spec.rb:52:in `block (5 levels) in <top (required)>'
I had assumed the spec wasn't logging in the user correctly, but a puts current_user.role.name confirms the user is logged in correctly, and has the necessary role. Performing the actual process in a browser confirms it works as desired.
Anyone have any suggestions? I'm quite stumped. Controller below:
def create
#post = Post.new(params[:post])
#post.user = current_user
respond_to do |format|
if #post.save
flash[:notice] = "Post successfully created"
format.html { redirect_to(#post)}
format.xml { render :xml => #post, :status => :created, :location => #post }
else
format.html { render :action => "new" }
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
end
end
end
...And the spec
describe "with valid params" do
it "assigns a newly created post as #post" do
Post.stub(:new).with({'title' => 'test title'}) { mock_post(:save => true) }
post :create, :post => {'title' => 'test title'}
assigns(:post).should be(mock_post)
end
...And supporting stuff in the spec:
before(:each) do
#user = Factory(:admin)
sign_in #user
end
def mock_post(stubs={})
#mock_post ||= mock_model(Post, stubs).as_null_object
end
Many thanks...

Try upgrading CanCan to version 1.5. I had the issue earlier but I think it went away when I upgraded.

Related

Getting nilClass error while testing with rspec

I'm testing my project with rspec and now I'm up to the controllers part. I'm testing this method:
def accept
#app = App.find(params[:id])
#invite = Invite.find_by(app: #app.name, receiver: current_dev.id)
#dev = Developer.find(#invite.sender)
#app.developers << #dev
respond_to do |format|
if #invite.destroy
format.html { redirect_to #app, notice: 'A new developer joined your team!' }
format.json { render :show, status: :destroyed, location: #app }
else
format.html { render :back }
format.json { render json: #invite.errors, status: :unprocessable_entity }
end
end
end
and this is the test part:
it "should accept an invite (1)" do
invite = Invite.create(:app => "test", :sender => "2", :receiver => "1")
get :accept, :id => 1
assert_response :success
end
but when I run the rspec command I get this error:
InvitesController should accept an invite (1)
Failure/Error: #dev = Developer.find(#invite.sender)
NoMethodError:
undefined method `sender' for nil:NilClass
So I am assuming that the invite object is nil but I can't figure out why this happens. I test the function via browser and everything works fine. This is also causing same errors in different controller methods, every time just because my invite object is nil. Why is this happening?
SOLVED:
Main reason things weren't working: mismatch between created invite parameters and current parameters (app, current_developer)
Debug: setting breakpoints/printing values of what was needed in the controller and what was needed in the model.
Fixing: created objects that were missing in order to match parameters; correct solution was
it "should accept an invite (1)" do
invite = Invite.create(:app => #app.name, :sender => "2", :receiver => #developer.id)
get :accept, :id => #app.id
assert_response :redirect
end

Testing Create Action in Rails Controller

Testing Rails 4 Application with RSpec 3 throws an Argument Error (2 for 0) when running a test on posts#create.
My controller:
def create
#post = Post.new(post_params)
respond_to do |format|
if #post.save
format.html { redirect_to(#post,
:notice => 'Post was successfully created.') }
format.json { render :json => #post,
:status => :created, :location => #post }
else
format.html { render :action => "new" }
format.json { render :json => #post.errors,
:status => :unprocessable_entity }
end
end
end
private
def post_params
params.require(:post).permit(:title, :body)
end
My Routes:
resources :posts
My Test:
describe 'POST posts#create' do
it 'allows admin to create a new post' do
sign_out user
sign_in admin
post :create, post: { title: 'Title', body: 'Body' }
expect(response).to have_http_status(:success)
end
end
The problem is on the post :create... line of the controller test. I'm unsure of why it is not running correctly. Solutions I have attempted:
1 post :create, post: title: 'Title', body: 'Body'
Thinking there was something wrong about my syntax. This however, throws an error.
2 post :create, { title: 'Title', body: 'Body' }
I thought that the post might be implicit since it is the name of the controller. No dice.
3 post :create, post:(post_params)
Again, I thought that because I have set up the usual permitted params, that they would be necessary. Not surprisingly, the test doesn't have post_params in its current scope.
4 post :create, post: { admin: { title: 'Title', body: 'Body' } }
I had before read that you have to pass in the user creating the post item. Incorrect.
5 post :create=> { title: 'Title', body: 'Body' }
I thought it might be the case that posting implicitly creates a post because that is the controller. However, it returns the argument error.
6 post :post=> { title: 'Title', body: 'Body' }
Finally, I thought that I have to post a new post a give the parameters without having to use the create action.
Edit - 7 post(:create, post: { title: 'Title', body: 'Body' })
After reviewing the API docs (http://api.rubyonrails.org/classes/ActionController/TestCase.html), I thought this would work but it's still the same argument error.
8
describe 'POST admin#create' do
it 'allows admin to create a new post' do
sign_in admin
expect{
post :create, post: {title: 'Title', body: 'Body' }
}.to change(Post, :count).by(1)
end
end
I attempted to use expect as a block thinking that it would pick up the arguments. Nope.
Compromise Solution
describe 'POST admin#create' do
it 'allows admin to create a new post' do
sign_in admin
expect{
Post.create({title: 'Title', body: 'Body' })
}.to change(Post, :count).by(1)
end
end
I think the name of the action being the name of the controller threw RSpec off. So, instead I just manually created a post. Probably not the recommended solution.
None of these worked. What am I missing?
Error Output
Failure/Error: post(:create, post: { title: 'Title', body: 'Body' })
ArgumentError:
wrong number of arguments (2 for 0)
# ./spec/controllers/posts_controller_spec.rb:101:in `block (3 levels) in <top (required)>'
I have also taken off all of my before_filters in order to see if that was the problem, however, it gives the same error.
Your syntax looks correct. It would look like this:
describe 'POST admin#create' do
it 'allows admin to create a new post' do
sign_out user
sign_in admin
post(:create, {post: {title: 'Title', body: 'Body'}})
expect(response).to have_http_status(:success)
end
end
...but the parens and outer curlys are optional.
The error is not coming from your test. The error occurs during the post to :create. It is coming from inside the controller or the model during the :create request.

How to send params with FactoryGirl (as opposed to manually sending the params as a hash)?

I have the following rspec test that works:
it "redirects to the created api_key" do
post :create, :api_key => {:api_identifier => "asdfadsf", :verification_code =>
"12345"}
response.should redirect_to(ApiKey.last) #(or any other test function)
end
But I use Factory girl so I don't have to manually create api_keys.
How can I replicate the above functionality, but use factory girl?
Using:
it "redirects to the created api_key" do
test = FactoryGirl.build(:api_key)
post :create, :api_key => test
response.should redirect_to(ApiKey.last) #(or any other test function)
end
or:
it "redirects to the created api_key" do
post :create, FactoryGirl.build(:api_key)
response.should redirect_to(ApiKey.last) #(or any other test function)
end
Gives me null values for the :api_key value when I arrive at my controller.
For reference, here is my create action that this test is testing:
def create
#api_key = ApiKey.new(params[:api_key])
#api_key.user = current_user
pp #api_key
respond_to do |format|
if #api_key.save
format.html { redirect_to #api_key, notice: 'Api key was successfully created.' }
format.json { render json: #api_key, status: :created, location: #api_key }
else
format.html { render action: "new" }
format.json { render json: #api_key.errors, status: :unprocessable_entity }
end
end
end
try:
post :create, :api_key => FactoryGirl.attributes_for(:api_key)
Using build doesn't actually create a record. It just pretends it did. Using attributes_for will give you the attributes of an object. This is mostly used in the context you describe. Note that this too will not create an object.
What I would do is this if the response is successful/redirect:
response.should be_redirect
Or even better use expect.

rails rspec - (2nd test) Expected response to be a <:redirect>, but was <200>

Expected response to be a <:redirect>, but was <200>
My tests has:
describe "Link POST #create" do
context "with valid attributes" do
it "creates a new link" do
expect{
post :create, link: FactoryGirl.create(:link, :group => #group)
}.to change(Link,:count).by(1)
end
it "redirects to the new link" do
post :create, link: FactoryGirl.create(:link, :group => #group)
# response.should redirect_to #link # Link.unscoped.last
response.should redirect_to Link.unscoped.last # render_template :show
end
end
The first test passes but the second fails.
My code is:
def create
#link = Link.new(params[:link])
respond_to do |format|
if #link.save
flash[:notice] = 'Link was successfully created.'
format.html { redirect_to(#link) }
format.xml { render :xml => #link, :status => :created, :location => #link }
else
#selected_group = params[:group_id]
format.html { render :action => "new" }
format.xml { render :xml => #link.errors, :status => :unprocessable_entity }
end
end
end
I've tried redirect and render but can't get the 2nd test to pass.
Here's something that should make it work:
describe "Link POST #create" do
context "with valid attributes" do
def do_post( format = 'html' )
attributes = FactoryGirl.build(:link).attributes.merge( :group_id => #group.id )
post :create, :link => attributes, :format => 'html'
end
it "creates a new link" do
expect{
do_post
}.to change(Link,:count).by(1)
end
it "redirects to the new link" do
do_post
response.should redirect_to( assigns[:link] )
end
end
The first spec was working only because you were calling FactoryGirl.create so a record was being created out of the controller, but most likely the controller call wasn't working.

Cannot test with rspec controller POST create action( devise and cancan)

I am having difficulty getting a rspec test for a controller to pass. I would like to test that the POST create action works. I am using rails (3.0.3), cancan (1.4.1), devise (1.1.5), rspec (2.3.0)
The model is dead simple
class Account < ActiveRecord::Base
attr_accessible :name
end
The controller is standard as well (straight out of scaffolding)
class AccountsController < ApplicationController
before_filter :authenticate_user!, :except => [:show, :index]
load_and_authorize_resource
...
def create
#account = Account.new(params[:account])
respond_to do |format|
if #account.save
format.html { redirect_to(#account, :notice => 'Account was successfully created.') }
format.xml { render :xml => #account, :status => :created, :location => #account }
else
format.html { render :action => "new" }
format.xml { render :xml => #account.errors, :status => :unprocessable_entity }
end
end
end
and the rspec test I would like to pass is (excuse the title, perhaps not the most appropriate one)
it "should call create on account when POST create is called" do
#user = Factory.create(:user)
#user.admin = true
#user.save
sign_in #user #this is an admin
post :create, :account => {"name" => "Jimmy Johnes"}
response.should be_success
sign_out #user
end
Yet all I get is
AccountsController get index should call create on account when POST create is called
Failure/Error: response.should be_success
expected success? to return true, got false
# ./spec/controllers/accounts_controller_spec.rb:46
Other actions can be tested and do pass (i.e. GET new)
here is the test for GET new
it "should allow logged in admin to call new on account controller" do
#user = Factory.create(:user)
#user.admin=true
#user.save
sign_in #user #this is an admin
get :new
response.should be_success
sign_out #user
end
and for completion here is the ability file
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.admin?
can :manage, :all
else
can :read, :all
end
end
end
Any ideas? My guess is that I am using the wrong rspec expectation, since the code does work (it is just that the test does not perform as desired!)
response.should be_success returns true if the response code is in the range 200-299. But the create action redirects, so the response code gets set to 302, thus the failure.
You can test this by using response.should redirect_to. Check the output of the standard RSpec controller generator for an example, which might look like this:
it "redirects to the created account" do
Account.stub(:new) { mock_account(:save => true) }
post :create, :account => {}
response.should redirect_to(account_url(mock_account))
end
The rspec test that got the test to pass was (thanks to zetetic's advice):
it "should call create on account when POST create is called" do
#user = Factory.create(:user)
#user.admin = true
#user.save
sign_in #user #this is an admin
account = mock_model(Account, :attributes= => true, :save => true)
Account.stub(:new) { account }
post :create, :account => {}
response.should redirect_to(account_path(account))
sign_out #user
end

Resources