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).
Related
I'm developing a sign in/out function on a web app that i'm building for learning.
During the week, i wasn't able to pass the following rspec test:
describe "sucessfull log in" do
let(:user) {FactoryGirl.create(:user)}
before do
visit signin_path
fill_in "Email", with: user.email.upcase
fill_in "Password", with: user.password
click_button 'Sign in'
end
it {should have_title(user.name)}
it {should have_link('Account')}
it {should have_link('Profile', href: user_path(user))}
it {should have_link('Sign out', href: signout_path)}
end
Then yesterday and end up re writing this code again, but forgot to add that .upcase function. I Left just .upcase.email. Suddenly, the test passed.
Ok, i'm a newbie developer... so what happaned? Why the upcase function did not let rspec log in successfully?
thanks in advance!
Why are you upcasing the email? Would you be equally surprised if .reverse made it fail? You're changing the email address.
EXAMPLE#EMAIL.COM is not the same as example#email.com, strictly speaking. It might be equivalent for your application, but you need to make the application know that.
Are you using devise? If so, the config/devise.rb file will have:
# Configure which authentication keys should be case-insensitive.
# These keys will be downcased upon creating or modifying a user and when used
# to authenticate or find a user. Default is :email.
config.case_insensitive_keys = [ :email ]
Which should make what you're doing work. If you're not using it, whatever framework you're using will probably have a similar option, and if you're doing it yourself, you'll need to handle it yourself.
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.
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 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.
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