Does Capybara clean its cookies after each request? - ruby-on-rails

When I run this spec
describe "as wrong user" do
let(:user) { FactoryGirl.create(:user) }
let(:wrong_user) { FactoryGirl.create(:user, email: "wrong#example.com") }
before { sign_in user }
describe "submitting a GET request to the Users#edit action" do
before { get edit_user_path(wrong_user) }
specify { expect(response).to redirect_to(root_url) }
end
end
I get this error
Expected response to be a redirect to http://www.example.com/ but was a redirect to http://www.example.com/signin.
But I thought that after calling this
before { sign_in user }
User should be logged in. Why does that happen?
Btw, here is sign_in method
def sign_in(user, options={})
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
check "Remember me"
click_button "Sign in"
end

It looks like you are using both the capybara dsl (visit and so on) and request specs. The latter, being based on rails integration test has its own methods (get, post etc.) of sending requests.
Both of these track state such as cookies across multiple requests but they are completely separate: mixing the two only causes confusion. Capybara however doesn't give you a response object, but should be able to use page.current_url to check that the redirect has happened.
Alternatively leave your example as is, but change your sign_in helper to use the post method to submit the signing data.
Recent versions of rspec try to help you avoid this by not creating by default specs that have both of the DSLs builtin and instead have separate spec types (spec/api and spec/features) for each (see here).

You need to maintain session
session = Capybara::Session.new(:webkit, my_rack_app)
session.fill_in "Email", with: user.email
session.fill_in "Password", with: user.password
end
session.click_button 'Sign in'
Read more about Capybara here: http://www.rubydoc.info/github/jnicklas/capybara/master/Capybara/Session

Related

Authlogic session in Capybara doesn't exist immediately

My spec:
let(:user) { FactoryGirl.create(:user) }
context 'When user is logged in' do
scenario 'Log in' do
visit login_path
within '.new_user_session' do
fill_in 'username', with: user.email
fill_in 'password', with: user.password
click_on 'Log in'
end
visit new_search_path
expect(page).to have_text "Welcome #{user.name}!"
end
end
The issue is that when visiting new_search_path, even though the login in successful, the page behaves as if there is no user logged in. If I add a sleep(1) call right before visit new_search_path, everything works fine.
Anyone know why this is happening?
I'm using authlogic, capybara, and selenium.
The action triggered.by click_on can occur asynchronously. Therefore, if you do a visit immediately after it can cause the login request to abort and the session cookies never get set. To solve that you need to check for page text/content that indicates the login has succeeded. Something like
expect(page).to have_text 'You are now logged in'

Rspec: sign_in using capybara doesn't work in controller spec

I'm working on exercise 9 from chapter 9 in Rails Tutorial: http://ruby.railstutorial.org/chapters/updating-showing-and-deleting-users#fnref-9_9
"Modify the destroy action to prevent admin users from destroying themselves. (Write a test first.)"
I started with creating test:
users_controller_spec.rb
require 'spec_helper'
describe UsersController do
describe "admins-userscontroller" do
let(:admin) { FactoryGirl.create(:admin) }
let(:non_admin) { FactoryGirl.create(:user) }
it "should not be able to delete themself" do
sign_in admin
expect { delete :destroy, :id => admin.id }.not_to change(User, :count)
end
end
end
however, noticed that even if logic for prohibiting admin to delete himself is not implemented, test passes unless I change line
sign_in admin
to
sign_in admin, no_copybara: true
after this change test fails (as expected)
sign_in is in support\utilities.rb file and looks like this:
def sign_in(user, options={})
if options[:no_capybara]
# Sign in when not using Capybara.
remember_token = User.new_remember_token
cookies[:remember_token] = remember_token
user.update_attribute(:remember_token, User.hash(remember_token))
else
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
end
end
Does anyone know why it doesn't work with capybara? It looks like the "else" section of the above code fails/doesn't execute when using capybara but it doesn't return any error (e.g. that "Email" field is not found, so it looks like it's rendered)...
Other problem is that if I remove non_admin instead of admin:
expect { delete :destroy, :id => non_admin.id }.not_to change(User, :count)
test passes which means non_admin isn't deleted... why does it work for admin and not non_admin?
Question 2:
capybara is not supposed to work in request spec as of 2.0+, but Im using capybara 2.1 and rspec-rails 2.13.1 and it works just fine in request specs (actually that's even what tutorial tells us to do), doesn't even output any warning...

why capybara can't submit directly to put action?

I was watching Michael Hartl's Rails tutorial, In chapter 9.2.2, Hartl says that we can't use capybara to issue put/patch requests directly to a model,
This was the test code:
describe "for wrong users" do
let(:user) { FactoryGirl.create(:user) }
let(:wrong_user) { FactoryGirl.create(:user, email: "wrong#example.com") }
before { valid_signin user}
describe "when submitting a PATCH request to users#update" do
before { patch user_path(wrong_user) }
specify { expect(response).to redirect_to root_path}
end
end
and valid_signin is like this, intially:
def valid_signin(user, options = {})
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
end
These tests don't work, as when we issue a put request, we can't use capybara to do this.
So is it like this, that we can't use capybara to test any put/patch requests? And what we should do in general when we need to test put/patch requests and we can't use capybara?
Capybara is for Behaviour Driver Development. Who's behaviour? Human beings.
Can a human being patch? Can he put? He can't. Only computers can.
Can a human being visit, fill_in, click_button? Yes he can. This is what Capybara for.
Bottom line, put computers' actions into unit testing and controller testing, mimic human beings in integration testing by Capybara.

capybara/rspec: should GET/PUT tests be in different files?

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: "wrong#example.com") }
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: 'wrong#example.com') }
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
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: "wrong#example.com") }
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: "wrong#example.com") }
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:
rspec-rails and capybara 2.0: what you need to know
rspec-rails Capybara page
Update:
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) }
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

Rails - minitest - Testing pages that require login

I am working on tests but running in to a road block on pages that require a current_user. I am using minitest, capybara, factorygirl, and authlogic, in rails 3.2.9 with ruby 1.9.3p327. I installed minitest as a separate gem, and seem to have the test environment working correctly.
I have a factory that creates a valid user...I call that factory from in a test like this:
describe "UsersAcceptanceTest" do
it "must load and include content" do
FactoryGirl.create(:user)
visit users_path
page.must_have_content("cPanel")
end
end
The FAIL is correct in informing me that the content "cPanel" was not found (cPanel is a link available to logged in users). The fail error goes on to alert me that it was not found in "log in, forgot password, contact" ... which of course means that the test routed correctly to users_path, but was redirected by authlogic because the user is not logged in. Users cant create themselves in my system and therefor are not auto-logged in on create.
How to I also get the factory to create a new user session with the newly created user?
You can do it this way:
visit signin_path
fill_in 'email', with: user.email
fill_in 'password', with: user.password
click_button "Log in"
Just edit it according to your login page structure.
I don't know about minitest, but in rspec I'd create the separate method with this codedef sign_in...end and put it to support\utilities.rb.
Then your code would be looking like that:
describe "UsersAcceptanceTest" do
let(:user) { Factory(:user) }
subject { page }
it "must load and include content" do
sign_in user
visit users_path
it { should have_link("cPanel", href: cpanel_path) }
end
end
As you can see I've edited your code a little bit more.

Resources