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
Related
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.
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).
I'm adding more controllers to the admin section of the Padrino but I can't workout how to stub the current user or a session with Factory Girl or Mocha.
What is a good way for testing controller actions that need a current session?
Caveat: I've not used Padrino, and you've not given any code you've tried, so this is quite general and vague.
Alternative 1
Don't stub the session, instead use a testing framework like Capybara that sets up a cookie jar for you. Use an RSpec shared_context with before and after blocks that run the login.
I don't remember the exact syntax for Capybara and I'll leave you to look it up, but it would be something like this:
shared_context "When logged in" do
before do
visit "/login"
fill_in "username", user.name
fill_in "password", user.password
click "login!"
end
after do
# log out…
end
end
describe "Something that you need to be logged in for" do
let(:user) { OpenStruct.new({name: "blah", password: "blurgh" }) }
include "When logged in"
before do
visit "/only/authenticated/see/this"
end
subject { page }
it { should be_ok }
it { #… }
end
Alternative 2
Using Rack::Test, look at this answer
Alternative 3
Here are the authentication helpers, so you should stub logged_in? to return true and current_account to return the user double (whether that's from FactoryGirl or a let or wherever). That way your app won't ask for the information from session.
This solution seem to work
def set_current_user(user)
ApplicationController.stub(:current_user).and_return(user)
session[:identity_id] = user.id
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 guess the problem is that I do not know how to use factory girl with Rspec correctly. Or testing in rails correctly for that matter. Still think it is a bit weird though..
I have a class, User, with the following factory:
FactoryGirl.define do
factory :user do
name "admin"
email "admin#admin.com"
adminstatus "1"
password "foobar"
password_confirmation "foobar"
end
factory :user_no_admin, class: User do
name "user"
email "user#user.com"
adminstatus "2"
password "foobar"
password_confirmation "foobar"
end
...
My test looks like this:
...
describe "signin as admin user" do
before { visit login_path }
describe "with valid information" do
let(:user_no_admin) { FactoryGirl.create(:user_no_admin) }
let(:user) { FactoryGirl.create(:user) }
before do
fill_in "User", with: user.name
fill_in "Password", with: user.password
click_button "Login"
end
it "should list users if user is admin" do
response.should have_selector('th', content: 'Name')
response.should have_selector('td', content: user_no_admin.name)
response.should have_selector('td', content: user.name)
end
end
end#signin as admin user
...
Basically I am trying to test that if you log in as an admin, you should see a list of all the users. I have a test for logging on as a non-admin later on in the file. I have a couple of users in the db already.
In the list of users 'admin' that logged in is displayed along with the users already in the db. 'user' is however not displayed unless I do something like this before:
fill_in "User", with: user_no_admin.name
fill_in "Password", with: user_no_admin.password
It is as if it won't exist unless I use it. However, if I use a puts it does print the information I am putting, even if I do not do the 'fill_in' above.
I have a similar example where a puts helps me.
describe "should have company name" do
let(:company) { FactoryGirl.create(:company) }
let(:category) { FactoryGirl.create(:category) }
let(:company_category) { FactoryGirl.create(:company_category, company_id: company.id, category_id: category.id) }
it "should contain companies name" do
puts company_category.category_id
get 'categories/' + company.categories[0].id.to_s
response.should have_selector('h4', :content => company.name)
end
end
Without the puts above I get a
Called id for nil
Do I have to initiate(?) an object created by Factory girl before I can use it in some way?
Any other code needed?
let(:whatever)
Is not creating the objects until the first time you call them. If you want it to be available before first use, use
let!(:whatever)
instead.
Or use a before block:
before(:each) do
#company = FactoryGirl.create(:company)
....
end
Which will create the objects before you need to use them.
Instead of:
factory :user do
name "admin"
email "admin#admin.com"
...
I will do:
factory :user do |f|
f.name "admin"
f.email "admin#admin.com"
...
Instead of:
let(:user_no_admin) { FactoryGirl.create(:user_no_admin) }
let(:user) { FactoryGirl.create(:user) }
I will do:
#user_no_admin = Factory(:user_no_admin)
#user = Factory(:user)
I had a similar issue with an existing test I broke, with a slightly different cause that was interesting.
In this case, the controller under test was originally calling save, but I changed it to call save!, and updated the test accordingly.
The revised test was:
Declaring the instance a let statement
Setting an expectation on the save! method (e.g. expect_any_instance_of(MyObject).to receive(:save!) )
Using the instance for the first time after the expectation.
Internally, it would appear that FactoryGirl was calling the save! method, and after changing the expectation from save to save!, no work was actually done (and the code under test couldn't find the instance from the DB)
that I needed to update and had a hard time getting to actually pass without a hack)
Try to use trait in the factory girl,there is an example as mentioned in the this link