Reputation: 1397
I'm following the Ruby on Rails Tutorial, and now I need to write tests for the authorization code, e.g. making sure users can only edit their own profile.
There are two actions to test. One is to ensure a user can't access the page of editing other users' profile. This one is easy, a simple "feature" test in capybara.
But I certainly want to test the PUT action too, so that a user can't manually submit a PUT request, bypassing the edit page. From what I read, this should be done as an rspec "request" test.
Now my question is, do I have to maintain them in different dirs? (spec/features vs spec/requests)? It doesn't sound right since these two scenarios are closely related. How are such tests usually done in Rails?
For example,
describe "as wrong user" do
let(:user) { FactoryGirl.create(:user) }
let(:wrong_user) { FactoryGirl.create(:user, email: "[email protected]") }
before { sign_in user }
describe "visiting Users#edit page" do
before { visit edit_user_path(wrong_user) }
it { should_not have_selector('title', text: full_title('Edit user')) }
end
describe "submitting a PUT request to the Users#update action" do
before { put user_path(wrong_user) }
specify { response.should redirect_to(root_path) }
end
end
The second test doesn't work in capybara 2.x since "put" is not supported any longer. It has to be a request test. And now I have to write a second "sign_in" method, since the current one uses methods that are only available to feature tests. Smells like a lot of code duplication.
======== my solution ========
After figuring out how to login in a request test, thanks to Paul Fioravanti's answer,
before do
post sessions_path, email: user.email, password: user.password
cookies[:remember_token] = user.remember_token
end
I changed all tests to request tests. So I don't have to split them into different files. Paul's solution would also work though I think this is cleaner.
describe 'authorization' do
describe 'as un-signed-in user' do
let(:user) { FactoryGirl.create(:user) }
describe 'getting user edit page' do
before { get edit_user_path(user) }
specify { response.should redirect_to(signin_path) }
end
describe 'putting to user update page' do
before { put user_path(user) }
specify { response.should redirect_to(signin_path) }
end
end
describe 'as wrong user' do
let(:user) { FactoryGirl.create(:user) }
let(:wrong_user) { FactoryGirl.create(:user, email: '[email protected]') }
before do
post sessions_path, email: user.email, password: user.password
cookies[:remember_token] = user.remember_token
end
describe 'getting user edit page' do
before { get edit_user_path(wrong_user) }
specify { response.should redirect_to(root_path) }
end
describe 'putting to user update page' do
before { put user_path(wrong_user) }
specify { response.should redirect_to(root_path) }
end
end
end
Upvotes: 3
Views: 1525
Reputation: 16793
I ended up going through the arduous process of splitting up my request and feature specs after I finished The Rails Tutorial and upgraded my Sample App to Capybara 2.0. Since you say you're still currently doing the tutorial, I would advise you to just keep with the gems that Hartl specifies (Capybara 1.1.2), finish your Sample App, and then go back to the requests/features issue as a refactoring exercise. For your reference though, this is how I ended up writing my "wrong user" authorization specs:
spec/support/utilities.rb
def sign_in_through_ui(user)
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign In"
end
def sign_in_request(user)
post session_path(email: user.email, password: user.password)
cookies[:remember_token] = user.remember_token
end
RSpec::Matchers::define :have_title do |text|
match do |page|
Capybara.string(page.body).has_selector?('title', text: text)
end
end
spec/features/authentication_pages_spec.rb
describe "Authentication on UI" do
subject { page }
# ...
describe "authorization" do
# ...
context "as a wrong user" do
let(:user) { FactoryGirl.create(:user) }
let(:wrong_user) { FactoryGirl.create(:user, email: "[email protected]") }
before do
visit root_path
click_link "Sign In"
sign_in_through_ui(user)
end
context "visiting Users#edit" do
let(:page_title) { full_title("Edit User") }
before { visit edit_user_path(wrong_user) }
it { should_not have_title(page_title) }
end
end
end
end
spec/requests/authentication_requests_spec.rb
describe "Authentication Requests" do
subject { response }
# ...
describe "authorization" do
# ...
context "as a wrong user" do
let(:user) { FactoryGirl.create(:user) }
let(:wrong_user) { FactoryGirl.create(:user, email: "[email protected]") }
before { sign_in_request(user) }
context "PUT Users#update" do
before { put user_path(wrong_user) }
it { should redirect_to(root_url) }
end
end
end
end
I primarily used the following two links as reference when trying to figure out how to separate my feature
specs from my request
specs:
If you don't want the custom RSpec matcher, you can also use the following in the tests above to get the same result on the title
element:
its(:source) { should have_selector('title', text: page_title) }
Upvotes: 2
Reputation: 2399
According to Jnicklas (https://github.com/jnicklas/capybara) you should move all Capybare specs you have in spec/requests to spec/features, since spec/features will now be used by Capybara 2.x. So this means that once you moved your Capybara specs to features, you could completely remove these specs from the spec/requests directory.
Personally, I've finished the Ruby on Rails tutorial with no problems at all. I used Capybara 2.x and never used spec/features (just the 'old' spec/requests). For Rspec 2.x support you have to add require >'capybara/rspec'< to your spec_helper.rb file. Without it, your tests could fail.
Edit:
I've just read trough the Rspec docs. If you are using Capybara in your specs these specs have to be moved to spec/features. If there is no Capybara involved the specs can simply stay in your requests directory.
Feature specs https://www.relishapp.com/rspec/rspec-rails/v/2-12-2/docs/feature-specs/feature-spec!
Request specs https://www.relishapp.com/rspec/rspec-rails/v/2-12-2/docs/request-specs
More info, from Rubydoc: http://rubydoc.info/github/jnicklas/capybara/master#Using_Capybara_with_RSpec
Upvotes: 1