In my app, I have an identical form on two separate pages. I've written a series of tests for the one page, and would like to simply loop over the test suite for my second page, passing in the page for which the test suite should run against. The problem is, I can't seem to access the url_helpers in a clean way.
This is what I have so far that works:
require 'rails_helper'
describe "my tests" do
subject { page }
paths = ["/signin", "/signup"]
paths.each do |path|
describe "user signs in via #{path}" do
before { visit path }
describe "user enters invalid information" do
before do
fill_in "Email", with: user.email, match: :first
fill_in "Password", with: user.password.reverse, match: :first
click_button "Sign in"
end
it { should have_title("Sign in") }
it { should have_content "Invalid email/password combination!" }
its(:current_path) { should eq "/signin" }
end
end
end
If I replace "/signin" with signin_path, I get
undefined local variable or method `signin_path' for RSpec::ExampleGroups::Authentication:Class (NameError)
But I'm using url_helpers successfully throughout the rest of my test script, albeit inside before and it blocks. If I fully qualify the url_helper with Rails.application.routes.url_helpers.signin_path it works, but I'd rather have this work automatically. If I do an include at the top of the script, I get
ActionView::Template::Error:
arguments passed to url_for can't be handled. Please require routes or provide your own implementation
Again, not ideal. I'd rather have this just work. Any thoughts?
Thanks in advance.
Are there no errors if you just did one path? If so, then why not use a RSpec shared_example
require 'rails_helper'
shared_examples 'get_user_in' do |path|
describe "user signs in via #{path}" do
before { visit path }
describe "user enters invalid information" do
before do
fill_in "Email", with: user.email, match: :first
fill_in "Password", with: user.password.reverse, match: :first
click_button "Sign in"
end
it { should have_title("Sign in") }
it { should have_content "Invalid email/password combination!" }
its(:current_path) { should eq "/signin" }
end
end
end
describe "/signin" do
subject { page }
include_examples "get_user_in", 'signin'
end
describe "/signup" do
subject { page }
include_examples "get_user_in", 'signup'
end
Didn't test this out, so there might be some error here.
Related
I have the following test...
spec/features/users/sign_in_spec.rb
require "rails_helper"
feature "User sign in" do
extend SubdomainHelpers
let!(:account) { FactoryGirl.create(:account) }
let(:sign_in_url) { "http://#{account.subdomain}.example.com/sign_in" }
let(:root_url) { "http://#{account.subdomain}.example.com/" }
within_account_subdomain do
scenario "signs in as an account owner successfully" do
visit root_url
expect(page.current_url).to eq(sign_in_url)
fill_in "Email", :with => account.owner.email
fill_in "Password", :with => "password"
click_button "Sign in"
expect(page).to have_content("You are now signed in.")
expect(page.current_url).to eq(root_url)
end
end
end
Note the 3rd line extend SubdomainHelpers which I am trying to load from...
spec/support/subdomain_helpers.rb
module SubdomainHelpers
def within_account_subdomain
let(:subdomain_url) { "http://#{account.subdomain}.example.com" }
before { Capybara.default_host = subdomain_url }
after { Capybara.default_host = "http://www.example.com" }
yield
end
end
When I run the text I get the error uninitialized constant SubdomainHelpers (NameError) How do I call the SubdomainHelpers module from the test?
You need to require 'support/subdomain_helpers' in your rails_helper or the sign_in_spec file.
Files in the spec/support subdirectory are automagically loaded into LOAD_PATH by rspec, but they're not required.
I'm trying requests tests with Capybara and it doesnt seem to work. This is my test file:
describe "Sessions", :type => :request do
let(:company) { FactoryGirl.create(:company) }
let(:user) { FactoryGirl.create(:admin, company_id: company.id ) }
describe "login page" do
it "signs me in" do
visit '/users/sign_in'
within("#new_user") do
fill_in 'Email', :with => user.email
fill_in 'Password', :with => user.password
end
click_button 'Sign in'
expect(page).to have_content 'Agenda'
end
end
end
Throws the following Error:
Failure/Error: expect(page).to have_content 'Agenda'
expected #has_content?("Agenda") to return true, got false
Im not sure if the problem is at logging in or at redirecting. But if i change the last line in the test for this:
expect(page).to have_content 'Invalid email'
I get the same Error.
Thanks in advance.
UPDATE:
i'm using devise for login
Simplest thing is to use screenshot_and_save_page to take a screenshot of the page just before the failing test, maybe you can determine what the problem is there.
With rspec, it's quite clear how you should organise your unit specs. The directory structure inside spec is very similar to that found in the app directory, so model specs go in the model directory, controller specs go in the controller directory and so on.
But it's not so clear with integration testing. I have just one file pertaining to integration testing: spec/features/integration.rb
Is the idea to create one elaborate spec that tests every faculty of your application? Something like this:
require 'spec_helper'
describe "Everything", js: true do
before do
#user_0 = FactoryGirl.build(:user_0)
#user_1 = FactoryGirl.build(:user_1)
#user_2 = FactoryGirl.build(:user_2)
#user_3 = FactoryGirl.build(:user_3)
end
it "can create a user" do
visit root_path
click_link 'Sign In'
ap #user_0
fill_in('Email', with: #user_0.email)
fill_in('Password', with: #user_0.password)
click_button 'Sign in'
visit('/user_friendships')
end
it "can create a user" do
end
it "can create a user" do
end
it "can create a user" do
end
it "GET /root_path" do
visit root_path
page.should have_content("All of our statuses")
click_link "Post a New Status"
page.should have_content("New status")
fill_in "status_content", with: "Oh my god I am going insaaaaaaaaane!!!"
click_button "Create Status"
page.should have_content("Status was successfully created.")
click_link "Statuses"
page.should have_content("All of our statuses")
page.should have_content("Jimmy balooney")
page.should have_content("Oh my god I am going insaaaaaaaaane!!! ")
end
end
But a lot longer?
Should I use more than one file? How should I use the describe blocks? I'm only using one at the moment and that doesn't feel right.
The short answer is: No, it's not meant to go into one file.
Bigger projects split their acceptance test-suite into files based on feature sets. If you have a lot of tests, they are often split up into different directories. The way that you organize those tests is up to you. I have seen a lot of different approaches here. I tend to group spec with similar requirements on database setup, aka test-data.
If you want to have great guide for your rspec tests, go and have a look at this site: http://betterspecs.org/
It's usually desirable to split integration tests per page or flow in your application.
For example, you could try:
# spec/users/sign_in_spec.rb
RSpec.describe 'Users Sign In', js: true do
let!(:user) { FactoryGirl.build(:user_0) }
it "can sign in" do
visit root_path
click_link 'Sign In'
fill_in('Email', with: user.email)
fill_in('Password', with: user.password)
click_button 'Sign in'
page.should have_content('Signed In')
end
end
# spec/statuses/manage_status.rb
RSpec.describe 'Statuses', js: true do
it "should be able to post a status" do
visit root_path
page.should have_content("All of our statuses")
click_link "Post a New Status"
fill_in "status_content", with: "Everything is Alright"
click_button "Create Status"
page.should have_content("Status was successfully created.")
click_link "Statuses"
page.should have_content("All of our statuses")
page.should have_content("Everything is Alright ")
end
end
If you are looking for a way to organize your specs, you could use Capybara Test Helpers to encapsulate the code and make the tests more readable. For example:
# spec/users/sign_in_spec.rb
RSpec.describe 'Users Sign In', js: true, test_helpers: [:login] do
let!(:user) { FactoryGirl.build(:user_0) }
it "can sign in" do
visit root_path
login.enter_credentials(email: user.email, password: user.password)
current_page.should.have_content('Signed In')
end
end
# spec/statuses/manage_status.rb
RSpec.describe 'Statuses', js: true, test_helpers: [:statuses] do
it "should be able to post a status" do
visit root_path
current_page.should.have_content('All of our statuses')
statuses.post('Everything is Alright')
statuses.should_now.have_status('Everything is Alright')
end
end
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
I am using some of these tools for the first time. I have read through the docs but wanted to ask here exactly what I'm trying to achieve.
I have a set of users that I want to test some actions I can do in a controller spec. When each user is created, there are a set of callbacks that take place to create associated objects.
I'd like to have access to these user instances and the associated objects of that ActiveRecord class. So for example, a user will have a set of lists so I'd like to be able to call user1.lists for example.
Also, I'd like to isolate this setup at the top and use either let's or a before black. It seems that just calling let like this:
# will test that get_count_for_list will return 5
describe ApiController do
# why same name - seems really confusing!
let(:user) { FactoryGirl.create(:user) }
let(:user2) { FactoryGirl.create(:user2) }
doesn't call the associated callbacks. Is this correct? Or is it possibly a timing issue?
I like the syntax of using let and being able to access these objects in my ExampleGroups such as user.id but can't access user.lists. Currently I am doing something like:
# will test that get_count_for_list will return 5
describe ApiController do
# why same name - seems really confusing!
let(:user) { FactoryGirl.create(:user) }
let(:user2) { FactoryGirl.create(:user2) }
let(:user3) { FactoryGirl.create(:user3) }
before do
FactoryGirl.create(:user2)
FactoryGirl.create(:user3)
end
but feel that there has to be a better way. Am I creating these user's twice?
thx
edit 1
I've isolated the code in question here. The global_id value is created via a callback. It exists correctly in the db and can be accessed via the corresponding find_by_email's but using the user2 var's doesn't provide access.
require 'spec_helper'
# will test that get_count_for_list will return 5
describe ApiController do
# why same name - seems really confusing!
let!(:user) { FactoryGirl.create(:user) }
let!(:user2) { FactoryGirl.create(:user2) }
let!(:user3) { FactoryGirl.create(:user3) }
before do
session[:user_id]=user.id # works
end
describe 'FOLLOW / UNFOLLOW options' do
it 'shall test the ability to follow another user' do
puts "user1: " + user.global_id.to_s # doesn't output anything
u2=User.find_by_email('jo#jo.com') # corresponds to user2
post :follow, :global_id => user2.global_id # doesn't work
#post :follow, :global_id => u2.global_id #works
u3=User.find_by_email('su#su.com')
puts "user_3" + u3.global_id.to_s # outputs correct value
post :follow, :global_id => user3.global_id #doesn't work
#post :follow, :global_id => u3.global_id # works
post :unfollow, :global_id => user.following.sample(1)
response.code.should eq('200')
end
end
end
Check the rspec doc: https://www.relishapp.com/rspec/rspec-core/v/2-11/docs/helper-methods/let-and-let
Note that let is lazy-evaluated: it is not evaluated until the first time the method it defines is invoked. You can use let! to force the method's invocation before each example.
In other words if you use let along with factory_girl a record will not be created before let-variable invocation.
The correct code is:
# will test that get_count_for_list will return 5
describe ApiController do
# why same name - seems really confusing!
let!(:user) { FactoryGirl.create(:user) }
let!(:user2) { FactoryGirl.create(:user2) }
I just solved a similar sounding problem. My user authentication spec was not passing using 'let'.
my broken spec:
describe "signin" do
before { visit signin_path }
describe "with valid information", :js => true do
let(:user) { FactoryGirl.create(:user) }
before do
fill_in "email", with: user.email
fill_in "password", with: user.password
click_button "Log In"
end
it { should have_selector('a', text: "#{user.first} #{user.last}") }
end
end
This was not working. The log-in authentication was failing as if the user record was not actually in the database when my sessions controller tries to authenticate it. I tried replacing let with let! but that did not fix anything. Reading this post, and the link above explaining let and let! I realized that I should not be using let for this spec. Now I have a passing spec:
describe "signin" do
before { visit signin_path }
describe "with valid information", :js => true do
user = FactoryGirl.create(:user)
before do
fill_in "email", with: user.email
fill_in "password", with: user.password
click_button "Log In"
end
it { should have_selector('a', text: "#{user.first} #{user.last}") }
end
end