Cucumber vs Rspec what to use to check user permissions - ruby-on-rails

I'm using devise and cancan to ensure protection to some areas of my website.
What tool is best to check that a user with a role can access and another role cannot?
Actually I'm creating several cucumber features to make sure a user with admin role can see the page and all other roles cannot (receiving an error message). Is there a better way to do that?
I've read a lot about when to use cucumber over rspec and when user rspec over cucumber, and the general idea I got is that I should say with cucumber "the user says this" and with rspec make sure that under the hood all is working properly... by the way it's hard to apply this general concept at work, and to be honest I find it to be a waste of time. If I can check the final page is what is expected why should I test, for example, for controllers? I find it only worth it to test for model validations and model functions.
Any suggestion? Possibly something more practical than the RSpec book since I have to apply concepts asap.

Expanding from the comments.
Based on https://github.com/ryanb/cancan/wiki/Testing-Abilities I do the following to test.
Inside my Administrator spec .
require 'spec_helper'
require 'cancan/matchers'
describe Administrator do
describe "abilities" do
subject { ability }
let(:ability) { Ability.new(admin) }
let(:account) { FactoryGirl.create :account, isp: admin.isp }
context "is a helpdesk admin" do
let(:admin) { FactoryGirl.create :helpdesk_admin }
let(:mail_user) {FactoryGirl.create :mail_user, account: account}
let(:web_user) {FactoryGirl.create :web_user, account: account }
let(:radius_user) { FactoryGirl.create :radius_user, account: account}
it { should be_able_to(:change_password,mail_user)}
it { should be_able_to(:change_password,radius_user)}
it { should be_able_to(:change_password,web_user)}
it { should_not be_able_to(:manage, Account.new) }
end
context "is a realm admin" do
let(:admin) { FactoryGirl.create :realm_admin }
it{ should be_able_to(:manage, MailDomain.new)}
it{ should be_able_to(:manage, RadiusDomain.new)}
it{ should be_able_to(:manage, WebDomain.new)}
it{ should be_able_to(:manage, Administrator.new)}
end
end
This lets me test the abilities that Each Role has assigned
Then inside my features/ I do something like this for each controller, to make sure that I don't forget an authorize.
context "regular admin" do
let(:admin) {FactoryGirl.create(:admin)}
before(:each) do
visit login_path
fill_in "email" , with: admin.email
fill_in "password", with: admin.password
click_button "Sign in"
end
it "shoudln't allow them to add new admins" do
visit new_administrator_path
page.should have_content "You are not authorized to access this page."
end
end

Cucumber is better for business case testing e.g. Bill can't edit Ben's profile. RSpec may be better for more exhaustive analysis, though one would hope you can trust that both Devise and CanCan have done this for you.
I have a sample project that shows some interesting ways of using Cucumber to do work with authentication so you can write very simple features. It would not take much to extend these ideas to usage with roles. Hope its of some use
see here

Related

How to share a variable with many "it" examples in rspec

I am using let to create a user record using factory girl. However i want to use exactly the same variable across 2 tests in the context as the user_id and email are important to the external API i am sending.
However i had no luck making a single variable for using across the examples. Here is my current code
context "User" do
let(:user) { FactoryGirl.create(:user) }
it "should create user and return 'nil'" do
expect(send_preferences(user, "new")).to eq nil
end
it "should not create user preferences again after sending two consecutive same requests" do
expect(send_preferences(user, "new")).to eq "User preferences already saved. No need to re-save them."
end
it "should update user preferences" do
expect(send_preferences(user, "update")).to eq nil
end
end
any clues?
You can use lets within lets:
context "User" do
let(:email_address) { 'test#test.com' }
let(:user) { FactoryGirl.create(:user, email_address: email_address) }
You will then also have access to the email_address variable within all your tests.
This works because previously the email address was being randomly generated by the factory every time the user was created, as we hadn't set a value for it anywhere. So, we called the code below in each test:
send_preferences(user, "new")
It called the 'user' let which created a new user with a completely random email address (as we hadn't give it a specific email value). Therefore during the backend API call it was sending a different email address every time.
let(:user) { FactoryGirl.create(:user) }
However, when we defined the email address 'let' as 'test#test.com', and passed that into the user factory as in the code I provided, we overrode the randomly generated email address with our own static value, So, every time we call the code again:
send_preferences(user, "new")
It now triggers the user factory create which is also taking our new 'email_address' let, which is always set to a specific value of test#test.com every time it is called.
let(:email_address) { 'test#test.com' }
let(:user) { FactoryGirl.create(:user, email_address: email_address) }
Therefore, when the backend API call is made the email address is always what we set it to.
Also, as it is a let we can use that variable in any of the tests themselves if we wish. For example:
it 'should set the email address' do
expect(user.email_address).to eq(email_address)
end
It's quite hard to explain in a few sentences but let me know if that's still not clear.
Having an instantiated variable shared among multiple tests is an anti-pattern 90% of the time in my opinion.
The problem with doing something like the below is you will be creating objects in your db without doing a cleanup.
before(:all) do
#user = FactoryGirl.create :user
end
Sure, you can do a before(:after) block or use DatabaseCleaner, but I think it is much better practice for tests to be as standalone as possible. In your case, make your setup of a send_preferences event before making an expectation on what happens the second time:
context "User" do
let(:user) { FactoryGirl.create(:user) }
# ...
it "should not create user preferences again after sending two consecutive same requests" do
send_preferences(user, "new") # Setup
expect(send_preferences(user, "new")).to eq "User preferences already saved. No need to re-save them."
end
it "should update user preferences" do
send_preferences(user, "new") # Setup
expect(send_preferences(user, "update")).to eq nil
end
end

Is is it a good idea to test if records were displayed on a page with feature specs

I have a page where user should see items he created. So I decided I want a feature test for that(with rspec and capybara).
What I wanted to do something like this:
require 'spec_helper'
feature "Accounts pages" do
scenario "user views his accounts" do
user = create(:user)
sign_in user
expect(current_path).to eq accounts_path
expect(page).to have_content "Your accounts"
#here I want to check if records were loaded and/or displayed on page
end
end
So my questions:
Is it a good idea to test if records were loaded on a specific page in a feature spec, or should I write controller spec for that?
If it's ok to go with feature spec here, what would be idiomatically correct way to do that?
You can't use controller spec to test if records are shown by any page as controller doesn't care what's displayed and what's not. In that case using feature spec sounds just fine.

How can I build on the result of old expectations in a feature spec?

Let's say I'm testing how to create a Widget:
feature "widget management" do
scenario "creating a widget_1" do
visit root_url
click_link "New Widget"
fill_in "Name", with: "Awesome Widget"
click_button "Create Widget"
expect(page).to have_text("Widget was successfully created.")
end
end
Okay, great, but let's say I want to create another widget, and test how those two interact with each other. In a unit test I would have no trouble stubbing or using factory girl's create method to set up the hashes I need, but with a feature integration test, I want to test the whole application realistically, just to really, really make absolutely sure there are no bugs. I don't want to stub or use a create method, I want to literally create two different widgets using the form found at the root_url!
But if I do this:
feature "widget management" do
scenario "creating a widget_1" do
visit root_url
click_link "New Widget"
fill_in "Name", with: "Awesome Widget"
click_button "Create Widget"
expect(page).to have_text("Widget was successfully created.")
end
scenario "creating a widget_2" do
visit root_url
click_link "New Widget"
fill_in "Name", with: "Awesome Widget_2"
click_button "Create Widget"
expect(page).to have_text("Widget_2 was successfully created.")
end
end
Awesome Widget_2 is created in the database, but Awesome_Widget, from the last scenario, is no longer in the database. This is because my config.use_transactional_fixtures is set to true. I want the test database to be cleaned between expectations, though, at least in all of my unit specs, as they use contexts to set up the database in a certain way before each expectation.
But I don't want the database to be cleaned between each scenario in my extensive integration spec! I want to build on what's happened before. Is this the correct way to do it? Or should I keep transitional_fixtures and be stubbing/creating in a before block for all of my feature specs?
Maybe then create one long expectation that creates multiple widgets and makes them interact with each other in one huge it block?
I just want to get to emulate true behaviour! I want to go through the forms, make thousands of widgets (using a loop and a factory girl sequence) and watch it all work for peace of mind, (maybe using a headed server such as Selenium for extra certainty) before it goes live! Surely that's a sensible thing to want to do? It's been surprisingly tricky to do this!
I can understand stubbing in a request spec, because though you're testing a faculty of your app that uses controllers, models, views and active record, you're testing that feature of your app in isolation.
With a feature spec though, you're meant to be telling a story. A user (or whatever) does this, and then he does that, meanwhile, another user is created, he "friends" the first user, the first user "accepts" and so on. Don't really know how I can do this if the database is wiping itself between each expectation!
Basically, how can I turn transactional_fixtures off for certain specs, but have them on for other specs, and is this advisable?
Up for using database cleaner instead of transactional_fixtures!
Update
Okay, this seems to be a good setup for telling a 'story' with a feature spec:
(Note, I've only included the code relevant to setting this up, your spec_helper needs a few more things in it to get rspec, factory girl, guard, whatever working)
Gemfile
(adding the database_cleaner gem for better control when dropping tables)
gem 'database_cleaner'
spec/spec_helper.rb
(configuring the database_cleaner to drop all tables in the test database, also setting transitional fixtures to true so that all tables are dropped between expectations(this is overwritten in the feature spec itself using an instance method, which you'll see in a bit))
RSpec.configure do |config|
config.after(:suite) do
DatabaseCleaner.clean_with(:truncation)
end
config.use_transactional_fixtures = true
I18n.enforce_available_locales = true
config.order = "random"
end
spec/features/integration.rb
And finally, the feature spec that builds on old expectations to 'tell a story'. Note the instance method that overrides the spec_helper's configuration regarding transactional fixtures:
feature "widget management" do
self.use_transactional_fixtures = false
scenario "creating a widget_1" do
visit root_url
click_link "New Widget"
fill_in "Name", with: "Awesome Widget"
click_button "Create Widget"
expect(page).to have_text("Widget was successfully created.")
end
scenario "creating a widget_2" do
visit root_url
click_link "New Widget"
fill_in "Name", with: "Awesome Widget_2"
click_button "Create Widget" # Both exist in the database and so they can take part in the story!
expect(page).to have_text("Widget_2 was successfully created.")
end
end
I suppose the question has changed a little!
What are your thoughts on the above as a way to write feature specs?
By the way, if you're attempting this, or anything that uses database cleaner to manually remove information from your database, be aware you can get in a little mess if you're using active record (which you should be in an integration spec) and model validations! Basically, if data from the last spec is lingering in your database for some reason (if you've just turned transactional_fixtures off, for example), your specs could fail any uniqueness validations you have set up, because identical data is already exists in the database.
If you have database cleaner setup like above, it's set to clean the database when the suite finishes. Because your spec is hitting a validation error, it's never finishing, so database cleaner never cleans the database. And because the database hasn't been cleaned, when you run the spec again, your spec hits the validation error again, it still doesn't finish and data base cleaner still doesn't clean the database and so on into infinitum.
To cut a long story short, if you're getting validation errors, manually clean your tables.
You can do this with the sqlite3 shell (A little easier than rails console I think, but you can use that as well if you want. It's similar commands with any shell, postgres, mysql, whatever):
In the command line:
$ cd db
$ sqlite3 test.sqlite3
sqlite > DELETE FROM widgets;
You may need to run that command a few times to empty different tables depending on your spec. The syntax of the command: DELETE FROM [table name you wish to delete from];
sqlite > .exit
What are your thoughts on the above as a way to write feature specs?
I don't think it's a good idea to share data between scenarios. A scenario shouldn't rely on data from another scenario. This will cause problems if you randomize the execution order in your RSpec configuration.
Another problem that I see is that you are not really testing different scenarios.
feature "widget management" do
self.use_transactional_fixtures = false
scenario "creating a widget_1" do
visit root_url
click_link "New Widget"
fill_in "Name", with: "Awesome Widget"
click_button "Create Widget"
expect(page).to have_text("Widget was successfully created.")
end
scenario "creating a widget_2" do
visit root_url
click_link "New Widget"
fill_in "Name", with: "Awesome Widget_2"
click_button "Create Widget" # Both exist in the database and so they can take part in the story!
expect(page).to have_text("Widget_2 was successfully created.")
end
end
The only difference between the two scenarios is that you are varying the name but not in a relevant way.
You could improve this suite by varying the type of input (e.g. valid then invalid) in order to test more paths in your code. I would refactor that suite to look more like this:
describe "widget management" do
before do
visit root_url
click_link "New Widget"
fill_in "Name", with: name
click_button "Create Widget"
end
context "when creating a widget with a valid name" do
let(:name) { "Awesome Widget" }
it "returns a success message" do
expect(page).to have_text("Widget was successfully created.")
end
end
context "when trying to create a widget with an invalid name" do
let(:name) { "" }
it "returns an error message" do
expect(page).to have_text("Widget_2 must have a valid name.")
end
end
end

Testing as a logged in user in rails

I wanna test my functionals in my rails application, but the tests require a user/admin to be logged in. I have looked at many websites, but I cannot grasp what I really should be doing to make it work. I'm sure there there is an easy thing to do, but I cannot seem to find it.
I have seen the stubs command used, but I only get that it is an undefined method and I don't really know what it is supposed to do.
Any ideas on how I can run my tests as if it was an user/admin requesting them?
Thanks for any help I might get.
acceptance/integration testing:
If you're using cucumber, just login as the user. Something like:
fill_in 'users[username]', :with => "my username"
fill_in 'users[password]', :with => "my password"
click_button "Log In"
If you're using rspec+capybara, you can do something like this (assuming you're using Devise for authentication)
# spec/spec_helper.rb
include Warden::Test::Helpers
# in spec/acceptance/some_spec.rb
...
login_as FactoryGirl.create(:user)
unit testing
see https://github.com/plataformatec/devise#test-helpers if you're using Devise
otherwise, you can implement similar functionality by writing to the session object, or by stubbing out current_user (assuming you call it that)

test for new user locking function I wrote for Rails Tutorial app

I made it up to Chapter 9 of the Ruby on Rails tutorial, and added functionality of my own to lock a user when they first sign up, such that an admin has to go in and approve ("unlock") their id before a new user has access to the site. I added a :locked boolean attribute that works just like the :admin attribute of the User object. I have that all working now, but I'm having trouble writing a simple test for it. I added the following test to user_pages_spec.rb, just under the hierarchy "pagination" - "as an admin user":
describe "as an admin user to unlock new users" do
let(:admin) { FactoryGirl.create(:admin) }
let(:locked_user) { FactoryGirl.create(:locked) }
before do
sign_in admin
visit users_path
end
it { should have_link('unlock', href: user_path(locked_user)) }
it { should_not have_link('unlock', href: user_path(admin)) }
end
and to support the creation of a "locked" user, added this to factories.rb:
factory :locked do
locked true
end
I can confirm manually through Firefox that the unlocking link shows up, but I'm still getting the following failure:
1) User pages index pagination as an admin user to unlock new users
Failure/Error: it { should have_link('unlock', href: user_path(locked_user)) }
expected link "unlock" to return something
# ./spec/requests/user_pages_spec.rb:64:in `block (5 levels) in <top (required)>'
I'm interested in knowing a) why this fails :), but also b) how to go about debugging a problem like this. How do I tell what the test is actually "seeing"? I tried rails-pry with a different problem as suggested by another stackoverflow user, but in this case I've found it of limited use.
Any ideas or suggestions would be much appreciated. Thanks in advance!
-Matt
You are supposed to write the test first ;)
Using your test as a start, I have been working through the process. I have gotten to the point of getting the same error as you. Using the pry-rails gem and putting binding.pry in the test:
it { binding.pry ; should have_link('unlock', href: user_path(locked_user)) }
(After messing around a lot) I copy and paste from the test to the command prompt:
should have_link('unlock', href: user_path(locked_user))
and get the error. Changing it to:
should have_link('unlock', href: user_path(User.first))
works. Entering locked_user at the prompt shows me the user record. Next I entered page.body and it shows me my locked user doesn't even show up on the page. (Confirmed by entering User.count and found it to be 33 so it was on page 2.) You may not have this problem depending on how deeply embedded your test is the specs. I realized I had embedded it inside another spec accidentally. When I moved it out (User.count == 2), it still didn't work. My locked_user still wasn't on the page. User.all didn't include the user either. Hartl mentions in chapter 10,
This uses the let! (read “let bang”) method in place of let; the reason is that let variables are lazy, meaning that they only spring into existence when referenced.
Changed the let to let! and it worked. (User.count == 3, including the locked_user this time.) Here is the test block.
describe "as an admin user" do
let(:admin) { FactoryGirl.create(:admin) }
before do
sign_in admin
visit users_path
end
...
describe "other users should have an unlock link" do ## changed the description
let!(:locked_user) { FactoryGirl.create(:locked) }
before { visit users_path }
it { should have_link('unlock', href: user_path(locked_user)) }
end
end
My code doesn't unlock anything yet (I'll need another test for that...) but the link shows up when it should. :)
it { should have_link('unlock', href: user_path(locked_user)) }
At first sight you need to use something like response.body.should have... or page.should.have....
Also render_views might be needed.
You probably want:
let!(:locked_user) { FactoryGirl.create(:locked) }
Notice that I'm using let!() as opposed to let(). See rspec docs for more info on let() vs let!() and why it matters here.

Resources