I am trying to test my cancancan abilities using rspec
but as opposed to testing for what a particular user can do, I am trying to test for what a user should not be able to do.
Now, I have a block of context like so:
context "for a manager" do
before do
#manager = FactoryGirl.build(:user, :manager)
#ability = Ability.new(#manager)
end
it "should not be able to create Questions" do
expect(#ability).not_to be_able_to(:create, Question.new)
end
it "should not be able to read Questions" do
expect(#ability).not_to be_able_to(:read, Question.new)
end
it "should not be able to update Questions" do
expect(#ability).not_to be_able_to(:update, Question.new)
end
it "should not be able to delete Questions" do
expect(#ability).not_to be_able_to(:destroy, Question.new)
end
end
This clearly shows that a user of type manager should not have any form of access to the Question model.
Is there a direct way to write this whole block in a single it block, with only one expect?
I have thought about writing it as follow:
context "for a manager" do
before do
#manager = FactoryGirl.build(:user, :manager)
#ability = Ability.new(#manager)
end
it "should not be able to manage Questions" do
expect(#ability).not_to be_able_to(:manage, Question.new)
end
end
But I'm thinking that this may not necessarily do what I'm intending it to do, as this test will pass is as much as one of the ability for that resource is not granted.
So, in short, is there a direct way to test such scenarios? Thanks to all.
First of all, I advise you to use an explicit subject for #ability so you can use the one-liner syntax like in the example below.
describe Role do
subject(:ability){ Ability.new(user) }
let(:user){ FactoryGirl.build(:user, roles: [role]) }
context "when is a manager" do
let(:role){ FactoryGirl.build(:manager_role) }
it{ is_expected.not_to be_able_to(:create, Question.new) }
it{ is_expected.not_to be_able_to(:read, Question.new) }
it{ is_expected.not_to be_able_to(:update, Question.new) }
it{ is_expected.not_to be_able_to(:destroy, Question.new) }
end
end
Updated after your comment
But you can also summarize all this 4 expectations to simply
%i[create read update destroy].each do |role|
it{ is_expected.not_to be_able_to(role, Question.new) }
end
Related
I have a custom AutorizationAdapter that I would like to test using RSpec:
class AdminAuthorization < ActiveAdmin::AuthorizationAdapter
def authorized?(_action, _subject = nil)
user.admin?
end
end
Initially I used a custom method but since I'm using Devise, using a custom AuthorizationAdapter seemed to be the way to go.
How would you go about testing it ? I tought one way to test it is to create a request spec for one of the controller and test for status code & redirection, something like that:
require 'rails_helper'
RSpec.describe 'AdminUsers', type: :request do
describe 'GET /admin_users' do
context 'admin' do
let(:admin_user) { create(:admin_user) }
before { sign_in super_user }
get admin_users_path
expect(response).to have_http_status(200)
end
context 'non admin' do
let(:user) { create(:user) }
before { sign_in user }
it 'redirects to the login page' do
get admin_users_path
expect(response).to have_http_status(302)
expect(response).to redirected_to '/admin/login'
end
end
context 'non logged in user' do
it 'redirects to the login page' do
get admin_users_path
expect(response).to have_http_status(302)
expect(response).to redirected_to '/admin/login'
end
end
end
end
I'm not sure this is the way to go.
These look reasonable to me. You can also look at the unit and feature specs that are in the ActiveAdmin test suite. However, AuthorizationAdapter itself is a PORO so you should be able to unit test in isolation: in the example given above that would be a fairly trivial test.
I am doing the thoughtbot intro to testing program. Im not sure how to test for what they want.
Below is my test.
require "rails_helper"
describe PeopleController do
describe "#create" do
context "when person is valid" do
it "redirects to #show" do
post :create, FactoryGirl.build_stubbed(:person)
expect(response).to redirect_to(show_people_path)
end
end
context "when person is invalid" do
it "redirects to #new" do
pending "create this test"
end
end
end
end
I am of course using factory girl. I have tried several methods. I really don't know hoe to test this controller.
Any insights would be great.
I would create an 'invalid' person using the FactoryGirl, and send it as a parameter to the post :create.
To create an invalid person record, why don't you use nested factories in FactoryGirl? Depending on the validation in your model, you can simply do something like:
spec/factories/person.rb
FactoryGirl.define do
factory :person do
...
factory :invalid_person do
...
email nil
...
end
end
end
in your test
context "when person is invalid" do
it "redirects to #new" do
post :create, FactoryGirl.build_stubbed(:invalid_person)
expect(response).to redirect_to action: :new
end
end
I am following Michael Hartl's excellent tutorial on Ruby on Rails. I'm stuck trying to understand the way ActionDispatch::Response works. This derives from Exercise 9 of Chapter 9 (Rails version 3.2.3).
In particular we're asked to make sure that the admin user is unable to User#destroy himself. I have an idea how to do that, but since I'm trying to follow a TDD methodology, I'm first writing the tests.
This is the relevant snippet in my test:
describe "authorization" do
describe "as non-admin user" do
let(:admin) {FactoryGirl.create(:admin)}
let(:non_admin) {FactoryGirl.create(:user)}
before{valid_signin non_admin}
describe "submitting a DELETE request to the Users#destroy action" do
before do
delete user_path(admin)
#puts response.message
puts response.succes?
end
specify{ response.should redirect_to(root_path) }
specify{ response.should_not be_success }
end
end
#Exercise 9.6-9 prevent admin from destroying himself
describe "as admin user" do
let(:admin){FactoryGirl.create(:admin)}
let(:non_admin){FactoryGirl.create(:user)}
before do
valid_signin admin
end
it "should be able to delete another user" do
expect { delete user_path(non_admin) }.to change(User, :count).by(-1)
end
describe "can destroy others" do
before do
puts admin.admin?
delete user_path(non_admin)
puts response.success?
end
#specify{response.should be_success}
specify{response.should_not be_redirect}
end
describe "cannot destroy himself" do
before do
delete user_path(admin)
puts response.success?
end
#specify{response.should_not be_success}
specify{response.should be_redirect}
end
end
.
.
.
end
All the tests pass except the "can destroy others" test.
However, if I puts response.success? after every delete request, I always get False, so none of the requests "succeed".
Manually interacting with the webapp and deleting users works just fine, so I assume that response.success does not mean that the detroy(or whatever request for that matter) was not successful, but something else. I read it has to do with the difference between HTTP responses 200/302/400, but I'm not totally sure.
For the record, this is my User#destroy:
def destroy
User.find(params[:id]).destroy
flash[:success]="User destroyed."
redirect_to users_path
end
Any light on this?
thanks!
Edit
This is my factory:
FactoryGirl.define do
factory :user do
sequence(:name){ |n| "Person #{n}" }
sequence(:email){ |n| "person_#{n}#example.com"}
password "foobar"
password_confirmation "foobar"
factory :admin do
admin true
end
end
end
Edit 2 as suggested by #Peter Alfvin, I changed lines
let(:user){FactoryGirl.create(:user)}
to
let(:admin){FactoryGirl.create(:admin)}
And all user to admin in general. I also added a puts admin.admin? before the delete request. Still not working!
Edit 3
Changing the test "can destroy others" as:
describe "can destroy others" do
before do
puts admin.admin?
delete user_path(non_admin)
puts response.success?
end
#specify{response.should be_success}
specify{response.should_not be_redirect}
end
Does not seem to help either.
For your "admin" case, you're still creating and logging in as a "regular" user instead of an admin user, which is why you can't destroy anyone else.
response.success does indeed refer to the HTTP response code. By default, I believe this is anything in the 200 range. redirect_to is in the 300 range.
Make sure your user Factory includes this line
factory :user do
#your user factory code
factory :admin do
admin true
end
end
Then FactoryGirl.create(:admin) will return an admin user or you can also use user.toggle!(:admin) which will switch a standard user to an admin user.
try this then
describe "as admin user" do
let(:admin){FactoryGirl.create(:admin)}
let(:non_admin){FactoryGirl.create(:user)}
before do
valid_signin admin
end
it "should be able to delete another user" do
expect { delete user_path(non_admin) }.to change(User, :count).by(-1)
end
it "can destroy others" do #
before do
puts admin.admin?
delete user_path(non_admin)
puts response.success?
end
#specify{response.should be_success}
specify{response.should_not be_redirect}
end
it "cannot destroy himself" do
before do
delete user_path(admin)
puts response.success?
end
#specify{response.should_not be_success}
specify{response.should be_redirect}
end
end
describe creates a magic Class it becomes a subClass of the describe class from my understanding. Rails has a lot of this magic and it can get confusing. Also I have not seen your controller but what are you expecting to happen when you destroy a user because if you followed the tutorial then there will be a redirect delete sent through the browser will call your destroy method in the UsersController which in the tutorial has this line redirect_to users_url so response.should_not be_redirect will always fail because the spec is wrong not the controller.
I'm trying to check if an administrator is signed out in an Rspec test. However the usual signed_in? method can't be seen from rspec and isn't part of the RSpec Devise Helpers.
Something like this is what i have in place
before (:each) do
#admin = FactoryGirl.create(:administrator)
sign_in #admin
end
it "should allow the admin to sign out" do
sign_out #admin
##admin.should be_nil
##admin.signed_in?.should be_false
administrator_signed_in?.should be_false
end
Is there anothe way to check the session of the administrator and see if he's actually signed in or not?
it "should have a current_user" do
subject.current_user.should_not be_nil
end
Found at https://github.com/plataformatec/devise/wiki/How-To:-Controllers-and-Views-tests-with-Rails-3-%28and-rspec%29
I think it's really what you need How To: Test controllers with Rails 3 and 4 (and RSpec)
Just check current_user. It should be nil
Add. Good practice is using syntax like this
-> { sign_out #admin }.should change { current_user }.from(#admin).to(nil)
Not a new answer, really, but my rep isn't high enough to comment...:
If you've already overridden subject, the controller is available as controller in controller specs, so:
expect { ... }.to change { controller.current_user }.to nil
To check for a specific user, say generated by FactoryGirl, we've had good success with:
let(:user) do FactoryGirl.create(:client) ; end
...
it 'signs them in' do
expect { whatever }.to change { controller.current_user }.to user
end
it 'signs them out' do
expect { whatever }.to change { controller.current_user }.to nil
end
it "signs user in and out" do
user = User.create!(email: "user#example.org", password: "very-secret")
sign_in user
expect(controller.current_user).to eq(user)
sign_out user
expect(controller.current_user).to be_nil
end
You can refer this link devise spec helper link
I am using Ruby on Rails 3.0.9 and RSpect 2. I am trying to refactoring some spec file in the following way (in order to test with less code similar User class object attribute values):
describe User do
let(:user1) { Factory(:user, :users_attribute_a => 'invalid_value') }
let(:user2) { Factory(:user, :users_attribute_b => 'invalid_value') }
let(:user3) { Factory(:user, :users_attribute_c => 'invalid_value') }
it "foreach user" do
[ user1, user2, user3 ].each do |user|
subject { user }
it "should be whatever"
user.should_not be_valid
...
end
end
end
end
However, if I run the above test I get the following error:
Failure/Error: it "should be whatever" do
NoMethodError:
undefined method `it' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_2::Nested_2:0x00000106ccee60>
What is the problem? How can I solve that?
UPDATE after the #Emily answer.
If in the above code I use context "foreach user" do ... instead of it "foreach user" do ... I get the following error:
undefined local variable or method `user1' for #<Class:0x00000105310758> (NameError)
The problem is having one spec nested within another. You need to replace it "foreach user" with context "foreach user".
Edited to add: After some investigation, it looks like helpers set with let are only available inside of the it "should ..." block, and not in the surrounding context. I'd recommend is trying to find a different structural solution. What the best solution is will depend on what you're actually trying to test. I'm guessing what you're trying to do is make sure the user is invalid when you remove any of the required attributes. In that case, what I've done is something like this:
describe User do
let(:user_attributes){ Factory.attributes_for(:user) }
# Testing missing values aren't valid
[:name, :email, :phone].each do |required_attribute|
it "should not be valid without #{required_attribute}" do
User.new(user_attributes.except(required_attribute)).should_not be_valid
end
end
# Testing invalid values aren't valid
[[:email, 'not_an_email'], [:phone, 'not a phone']].each do |(attribute, value)|
it "should not be valid with bad value for #{attribute}" do
User.new(user_attributes.update(attribute => value)).should_not be_valid
end
end
end
If you're doing something that requires more complex differences in the instance you're creating, there may not be a clean way to do it with iteration. I don't think DRY is quite as essential in testing as it is in other parts of your code. There's nothing wrong with having three different contexts for the three user types, and a validity test in each context.
describe User do
context "with user1" do
subject{ Factory(:user, :users_attribute_a => 'invalid_value') }
it{ should_not be_valid }
end
context "with user2" do
subject{ Factory(:user, :users_attribute_b => 'invalid_value') }
it{ should_not be_valid }
end
context "with user3" do
subject{ Factory(:user, :users_attribute_c => 'invalid_value') }
it{ should_not be_valid }
end
end
You're mixing and matching all sorts of rspec stuff. Here's your stuff, fixed:
describe User do
let(:user1) { Factory(:user, :users_attribute_a => 'invalid_value') }
let(:user2) { Factory(:user, :users_attribute_b => 'invalid_value') }
let(:user3) { Factory(:user, :users_attribute_c => 'invalid_value') }
it "should not be valid" do
[ user1, user2, user3 ].each do |user|
user.should_not be_valid
end
end
end
I would do it this way:
describe User do
subject{Factory.build(:user)}
it "should not be valid with invalid users_attribute_a" do
subject.users_attribute_a = "invalid_value"
subject.should_not be_valid
end
it "should not be valid with invalid users_attribute_b" do
subject.users_attribute_b = "invalid_value"
subject.should_not be_valid
end
end
If you want to have "context", then cool, but you can't have variables before your context inside of your context.
If you want to have a specification, then have one, but you can't net "it" statements
UPDATE WITH LEAST POSSIBLE CODE
describe User do
it "should not be valid with other attributes" do
{:users_attribute_a => 'invalid_value', :users_attribute_b => 'invalid_value', :users_attribute_c => 'invalid_value'}.each do |key, value|
Factory.build(:user, key => value).should_not be_valid
end
end
end
The problem is that the helpers that are set with "let" do not exist outside of a example context.
What you're trying to do could be achieved as:
it "does something with all users" do
[user1, user2, user3] do |user|
user.valid?.should be_true
end
end
Both contexts are different
Another way it might work (haven't tried it) it's like this:
context "for all users" do
[:user1, :user2, :user3].each do |user|
it "does something" do
send(user).valid?.should be_true
end
end
end
This should work. Note how the context is written, it will make the output of tests clearer. From writing it this way it implies (to me) that you should make a test for each attribute separately, but it's your choice:
describe User do
let!(:users) {
[:users_attribute_a, :users_attribute_b, :users_attribute_c].map do |a|
Factory(:user, => 'invalid_value')
end
}
context "Given a user" do
context "With an invalid value" do
subject { users }
it { subject.all?{|user| should_not be_valid }
end
end
end