RSpec and CanCan Controller Testing - ruby-on-rails-3

I'm using RSpec and CanCan in a project. I'm testing my permission logic in specs related to the Ability class. For the controllers I really just want to make sure I'm doing an authorization check. I set up a controller macro, but it doesn't seem to be working correctly.
So really I have two questions. One, is the strategy sufficient for testing the permission logic of my controllers (or should I be testing the controller authorization logic more)?
Two, does anyone see what I'm doing wrong to make this not work?
#plan_orders_controller.rb
def approve
plan_order = PlanOrder.find(params[:id])
authorize! :update, plan_order
current_user.approve_plan_order(plan_order)
redirect_to plan_order_workout_plan_url(plan_order)
end
#controller_macros.rb
def it_should_check_permissions(*actions)
actions.each do |action|
it "#{action} action should authorize user to do this action" do
#plan_order = Factory(:plan_order, :id=>1)
ability = Object.new
ability.extend(CanCan::Ability)
controller.stub!(:current_ability).and_return(ability)
get action, :id => 1
ability.should_receive(:can?)
end
end
end
The output I get from RSpec is the following
Failure/Error: ability.should_receive(:can?)
(#<Object:0x00000006d4fa20>).can?(any args)
expected: 1 time
received: 0 times
# ./spec/controllers/controller_macros/controller_macros.rb:27:in `block (2 levels) in it_should_check_permissions'
I'm not really sure which method I should be checking when I call !authorize in the controller (or it is automatically called via load_and_authorize_resource)

should_receive is an expectation of something that happens in the future. Reverse these two lines:
get action, :id => 1
ability.should_receive(:can?)
so you have this:
ability.should_receive(:can?)
get action, :id => 1

Related

New to Rails 4 Testing - Need help getting started (rSpec and Devise)

I'm relatively new to testing and very new to Rails 4 and rSpec. I am trying to test a controller that uses Devise for authentication and I am stuck. All of the examples I can find are for Rails 3.
I'm using Rails 4.0.3, Devise 3.2.3, rSpec 2.14.1 and FactoryGirl 4.4.0.
class LessonPlansController < ApplicationController
before_action :authenticate_user!
# GET /lesson_plans
def index
#lesson_plans = current_user.lesson_plans.to_a
end
.
.
.
private
# Use callbacks to share common setup or constraints between actions.
def set_lesson_plan
#lesson_plan = LessonPlan.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def lesson_plan_params
params[:lesson_plan]
end
def lesson_plan_params
params.require(:lesson_plan).permit(:title, :synopsis)
end
end
Here are my factory definitions: (Maybe I don't need to define user_id in the lesson_plan factory?)
FactoryGirl.define do
factory :user do
sequence( :username ) { |n| "user#{n}" }
sequence( :email ) { |n| "foo#{n}#example.com" }
password 'foobarbaz'
password_confirmation 'foobarbaz'
created_at Time.now
updated_at Time.now
end
end
FactoryGirl.define do
factory :lesson_plan do
user_id 1
title "The French Revolution"
synopsis "Background and events leading up to the French Revolution"
end
end
And the test part is where I get stuck.
describe LessonPlansController do
let(:valid_attributes) { { } }
let(:valid_session) { {} }
# describe "GET index" do
it "assigns all lesson_plans as #lesson_plans" do
user=FactoryGirl.create(:user)
sign_in user
lesson_plan = LessonPlan.create! valid_attributes
get :index, {}, valid_session
assigns(:lesson_plans).should eq([lesson_plan])
end
end
I'm not sure what to put in valid_attributes and valid_session (or if I even need them). The test will get as far as signing in the user, but will fail on creation of the lesson_plan. Admittedly this is the default/generated test for rSpec, but I am not sure how to proceed.
Examples I have seen use a before block to set up the user. I haven't been able to find anything on the Devise wiki page covering how to write basic rSpec tests for a controller that requires the user to be logged in. Any pointers would be greatly appreciated!
"I'm not sure what to put in valid_attributes and valid_session (or if I even need them)."
Well that depends what you're testing for.. Say you're testing validations & want to ensure that a record not be created if x column is set to null... then you could try to specifically create a record with invalid attributes (e.g. column: nil) and expect the result to not return true; maybe you want to ensure that it IS created with valid attributes.
You can btw, use `attributes_for(:factory_name)`` since you're using FactoryGirl. And no you don't necessarily need to specify the user's id in your lesson plan factory; unless you always want it to reference user 1. You can simply reference user with no value. Check out http://everydayrails.com/2012/03/12/testing-series-intro.html and especially parts 3-5 for an introduction to testing with RSPec.. I found this a pretty easy to follow guide when I was getting started.

Devise sign_in_params method missing

I'm attempting to use Devise (2.2.4), which I'm new to, with the Rails 3.2.13/Ruby 2.0.0p195 app I'm building. I turned scoped_views on because I want to have my own separate users and admins views. And I created my own Users::RegistrationsController which seems to be doing what I want it to. I've just added my own Users::SessionsController, which is where I've hit problems.
I straight copied over a couple of action methods from the Devise::SessionsController source as a first step, planning to modify them once they were working (my controller code is at the bottom of this post). But my 'new' method is failing, when called, with a NameError because `sign_in_params' is apparently undefined.
Well, that seems pretty strange because I'm inheriting from Devise::SessionsController, and when I look at the source for that on GitHub, there's the sign_in_params defined in the protected section at the bottom. So I decided to investigate whether my controller is inheriting correctly from Devise::SessionsController - and it certainly seem to be. I can list out all the inherited methods, just not that one missing one. So I ended up running the following piece of code in the Rails Console:
(Devise::SessionsController.new.methods - DeviseController.new.methods).each {|m| puts m}
And it produces the following output:
_one_time_conditions_valid_68?
_one_time_conditions_valid_72?
_callback_before_75
_one_time_conditions_valid_76?
new
create
destroy
serialize_options
auth_options
If I ignore the underscored methods, the remainder are all those methods defined in the Devise::SessionsController source except sign_in_params. I can't see how anything I've written can be deleting that method, and I can't think what else to try. Google is silent on this problem, so I assume I'm doing something uniquely foolish, but I can't work out what. Any suggestions please? And might someone else try running that bit of Rails Console code to see what they get?
class Users::SessionsController < Devise::SessionsController
prepend_before_filter :require_no_authentication, :only => [ :new, :create ]
prepend_before_filter :allow_params_authentication!, :only => :create
prepend_before_filter { request.env["devise.skip_timeout"] = true }
# GET /resource/sign_in
def new
self.resource = resource_class.new(sign_in_params)
clean_up_passwords(resource)
respond_with(resource, serialize_options(resource))
end
# POST /resource/sign_in
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => after_sign_in_path_for(resource)
end
end
I think you are using code from a devise version compatible with Rails 4 on a rails 3 application.
sign_in_params is a method to be used with strong parameters. A gem used in rails 4.
If you check the controller on devise version 2.2. https://github.com/plataformatec/devise/blob/v2.2/app/controllers/devise/sessions_controller.rb
You will see that there is no sign_in_params method.
Check which version of devise you are using and copy the code based on that devise version in your controller, rather than the latest code from github.

How to test Model Errors with Mocha and rspec?

I have this problem related to testing model errors with Mocha:
This is my controller (Api / Artist controller):
class Api::ArtistsController < ApplicationController
respond_to :json
def create
artist = Artist.new(params[:artist])
if artist.save <-- This is where the test fails
render :json=>artist
else
respond_with artist
end
end
end
This is my model (Artist Model):
class Artist < ActiveRecord::Base
include Core::BaseModel
attr_accessible :name
has_many :albums
validates :name, :presence=>true, :uniqueness=>{:case_sensitive=> false}
default_scope where :deleted=>false
end
This is the test where it fails, about Artist controller:
it "should not save a duplicated artist" do
Artist.any_instance.stubs(:is_valid?).returns(false)
Artist.any_instance.stubs(:errors).returns({:name=>[I18n.t('activerecord.errors.messages.taken')]})
post :create, :format => :json
expect(response).not_to be_success
expect(response.code).to eq("422")
results = JSON.parse(response.body)
expect(results).to include({
"errors"=>{
"name"=>[I18n.t('activerecord.errors.messages.taken')]
}
})
end
When I run the tests, this is the error I get on the above test:
Failure/Error: post :create, :format => :json
NoMethodError:
undefined method `add_on_blank' for {}:Hash
# ./app/controllers/api/artists_controller.rb:17:in `create'
# ./spec/controllers/api/artists_controller_spec.rb:56:in `block (3 levels) in <top (required)>'
I'm starting to use Mocha, so I don't know if there's a way to test the json result for the specific case when I want to test the validation for the duplicated name.
ActiveRecord::Base#errors (i.e. Artist#errors) isn't a simple hash. It's supposed to be an instance of ActiveModel::Errors. You're stubbing it with a hash, and ActiveRecord is trying to call add_on_blank on it, which is failing.
I don't think save invokes is_valid? at all, and I suspect it's running the validations and then trying to call add_on_blank to append an error, but since you've stubbed out errors, that's failing.
This isn't really a good way to test the controller. It's making too many assumptions about the internals of Artist. You're also testing things that aren't part of the controller at all; errors isn't referenced anywhere in the action. The only behavior worth testing in the controller is whether or not it creates an Artist; if that Artist fails to save, that it renders JSON with it; and if the save succeeds, that it redirects. That's all of the controller's responsibility.
If you want to test that errors are rendered a certain way, you should write a separate view spec. If you want to test that missing fields generate errors, you should write a model spec. If you don't want to write a view spec, it's still sufficient to rely on the model to populate errors (tested in a model spec), and in your controller, just test that render is called with json set to the Artist instance.
Generally speaking it's best to avoid stubbing as much as possible, but in this case, the only things I'd consider stubbing are Artist.new to return a mock, and save on that mock to return false. Then I'd check to make sure it rendered with the mock.
The easier option is to just create an actual Artist record, then call post with duplicate params to trigger a validation failure. The downside is that you hit the database, and avoiding that in a controller spec is laudable, but generally more convenient. You could instead do that in a Capybara feature spec if you want to avoid DB hits in your controller specs.
If you want to try testing the way you are, you can manually create an instance of ActiveModel::Errors and populate that, or stub methods on it, and stub out Artist.any_instance.stubs(:errors) to return your mock with ActiveModel::Errors-compatible behavior, but that's a lot of mocking.
One final tip: don't use post :create, :format => :json. Use xhr :post, :create to generate a real Ajax request rather than relying on a format param. It's a more robust test of your routing and response code.

redirect_to from "destroy" to "index"

Problem appears when I want to remove element from paginated table in "ajaxized" way. My tasks controller calls its destroy method in response to [DELETE] /tasks/1234, but at the end I want to redirect to index to get the list automatically refreshed.
Unfortunately, what redirect_to tasks_url at this point does is [DELETE] /tasks request.
Is there any way to force GET request instead of DELETE while redirecting from inside of destroy ?
Use redirect_to with status 303
def destroy
#task = Task.find(params[:id])
#task.destroy
redirect_to :action => :index, status: 303
end
redirect_to documentation says:
http://api.rubyonrails.org/classes/ActionController/Redirecting.html
If you are using XHR requests other than GET or POST and redirecting after the request then some browsers will follow the redirect using the original request method. This may lead to undesirable behavior such as a double DELETE. To work around this you can return a 303 See Other status code which will be followed using a GET request.
Don't use redirect. Use render. Abstract the functionality to get the table data into a library module and then call that from both index as well as the delete methods, and then add then add the render call to the delete method so that the index view is what is sent back in response.
require 'myLibrary'
include myModule
def index
getTableData
end
def destroy
flash.now[:notice] = "Delete operation failed" unless Task.destroy(params[:id])
getTableData
render 'myController/index'
end
in lib/myLibrary
module myModule
def getTableData
# Table Foo here
end
end
Ok, so to sum up this question. The easiest way I found to fix the issue, is to mess a bit in routes.rb by adding: "tasks" => "tasks#index", :via => :get and to use redirect_to tasks_url (in my case) directly in destroy action in controller.
This will solve the problem with kaminari pager as well (which renders strange links in some cases).
I think you should rearrange your code on the client side:
Memorize current page
fire destroy action, await true or false
request index at memorized page using ajax call.
Why not to use :action param ?
def destroy
#task = Task.find(params[:id])
#task.destroy
redirect_to :action => :index
end
You probably don't want to do a standard rails redirect when using ajax. This blog post has some good insight on the issue and solutions. Note this is from a similar question in January, answered by DGM

Rails 3: Manipulating devise "resource" in controller?

I'm using devise & devise_invitable in a rails 3 project, and I'm trying to manipulate some of the 'User' object fields in the devise controller.
The action is question is this:
def update
self.resource = resource_class.accept_invitation!(params[resource_name])
resource.first_name = 'Lemmechangethis'
if resource.errors.empty?
set_flash_message :notice, :updated
sign_in(resource_name, resource)
respond_with resource, :location => after_accept_path_for(resource)
else
respond_with_navigational(resource){ render_with_scope :edit }
end
end
I'd have thought that the (commented out) resource.first_name call would influence resource in much the same way as a model - but it doesn't seem to. I'm still getting a 'blank' validation error on this form.
So, the question is, how do I specify values to the User model in devise (and/or devise_invitable) that will actually be subject to verification?
Any suggestions appreciated,
John
resource does return a User models instance. So the resource.first_name = 'Lemmechangethis' statement does change your User models instance but it doesnot trigger your User models validations, which is probably why resource.errors always returns an empty array. One way to trigger the User models validation is to call resource.valid? for example. You can then check the resource.errors array for specific error messages.