why capybara can't submit directly to put action? - ruby-on-rails

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.

Related

Why do these tests fail when run simultaneously, yet each passes individually?

I am experiencing strange very test behavior, with logged in state being handled inconsistently.
The spec logs a user in, visits a (nested or un-nested) index page, and checks that the correct content is displayed. Records are fetched asynchronously, though I don't think this should have an impact.
When each spec is run individually, they each pass. When all specs are run together, they fail because the expected content is missing. Using save_and_open_page reveals this is because the login page is being rendered, rather than the expected index page.
Why does rspec think the user is not signed in when all specs are run together, yet each spec passes individually?
The tests look something like this
let(:user) {create :user}
let(:team) {create :team}
let(:country) {create :country}
before :each do
login_as( user, scope: :user )
end
describe 'unnested' do
it 'should have the expected content', :js do
visit users_path
is_expected.to have_content "some content on the page"
end
end
describe 'nested by team' do
it 'should have the expected content', :js do
visit team_users_path(team)
is_expected.to have_content "some content on the page"
end
end
describe 'nested by nationality' do
it 'should have the expected content', :js do
visit country_users_path(country)
is_expected.to have_content "some content on the page"
end
end
The specs all require javascript (I don't know whether that is important here).
Authentication is handled by Devise, and my rails_helper.rb includes
config.append_after(:each) do
DatabaseCleaner.clean
Warden.test_reset!
end
Why does rspec think the user is not signed in when all specs are run together, yet each spec passes individually?
It took a long time to get to the bottom of this. Posting this hear in case it is of help to anyone else encountering the same issue.
After much searching I eventually found this small mention that login_as may not work with Poltergeist when js is enabled on your test scenarios.
I tried the suggested fix to deal with shared DB connections. Unfortunately this resulted in the following errors:
PG::DuplicatePstatement at /session/users/signin
ERROR: prepared statement "a1" already exists
I tried using the Transactional Capybara gem, but this did not seem to work well with Poltergeist.
Eventually I abandonned login_as completely, and instead wrote a short method that visits the login page, fills in email and password, and logs in that way.
This solution appears to be working. It adds a little overhead, so I'm only using it for tests with JS.
If you are using Capybara gem then there is no need to use :js with test cases
What I did if this helps-
scenario "visit with user signed in" do
user = FactoryGirl.create(:user)
login_as(user, :scope => :user)
visit "/"
expect(current_path).to eq('/')
expect(page).to have_title "Some Random Title"
end
The other way you can login user using feature specs like-
feature 'User signs in' do
before :each do
#user = FactoryGirl.create(:user)
end
scenario "Signing in with correct credentials" do
visit "/"
fill_in "Email", with: #user.email
fill_in "Password", with: #user.password
click_button "Log In"
expect(current_path).to eq("/login/useremail/verification")
expect(page).to have_content "Signed in successfully"
end
end
If your pages are ajax then refer to this https://robots.thoughtbot.com/automatically-wait-for-ajax-with-capybara

Does Capybara clean its cookies after each request?

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

How do I define a variable in a RSpec feature?

Im learning Rails 4 and and trying to write some tests using Rspec and capybara. Im writing a feature test for my users and I'm trying to test a user signing in.
feature "User" do
scenario "A user signs in" do
let(:user) { FactoryGirl.create(:user) }
visit signin_path
fill_in "Username", with: user.username
fill_in "Password", with: "123456"
click_button "Log in"
expect(page).to have_content(user.username)
end
end
Its telling me that let is an undefined method. and I'm sure that the problem is that it is in a feature/scenario test. How do I define the user in this kind of test?
Also, in a describe/it request test like this
describe "Something" do
it "should do something" do
expect(page).to have_content{"...")
end
end
I can shorten it like this
describe "Something" do
subject { page }
it { should have_content("...") }
end
Is there a way to shorten the expect(page)..... in a scenario? Thanks.
let is used for lazy-initialization of "variables" as needed across multiple tests; initializing it in a test is nonsensical. Either move the let outside of the scenario block, or just use standard variable assignment, like user = FactoryGirl.create(:user).

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