I'm trying to run a Capybara 1.0 test on my Rails 3 app to test whether when a user clicks on a confirmation link, he is actually confirmed.
Now, this actually works when I test it manually. In addition, as you can see there is a puts #user.confirmed line that I put in the confirm method to debug this, and it actually prints true when I run the test. However, the test itself fails.
It seems as if the confirmed attribute in my user model isn't being remembered by the test after executing the controller method.
What am I missing? Thanks so much in advance.
Test:
it "should allow a user to be confirmed after clicking confirmation link" do
fill_in('user_email', :with => 'test#test.com')
click_button('Submit')
#user = User.find_by_email('test#test.com')
#user.confirmed.should be_false
visit confirm_path(#user.confirmation_code)
#user.confirmed.should be_true
end
Controller method:
def confirm
#confirmation_code = params[:confirmation_code]
#user = User.find_by_confirmation_code(#confirmation_code)
#website = #user.website
#user.confirm
if #user.referrer_id
User.find(#user.referrer_id).increment_signups
end
flash[:success] = "Thanks for signing up!"
flash[:user_show] = #user.id
puts #user.confirmed
redirect_to "http://" + #website.domain_name
end
User model method:
def confirm
self.confirmed = true
self.save
end
Is it that you would need to reload the user object after visiting the confirm_path? Try this:
it "should allow a user to be confirmed after clicking confirmation link" do
fill_in('user_email', :with => 'test#test.com')
click_button('Submit')
#user = User.find_by_email('test#test.com')
#user.confirmed.should be_false
visit confirm_path(#user.confirmation_code)
#user = User.find_by_email('test#test.com')
#user.confirmed.should be_true
end
Alternatively you could use #user.reload.
The user object referred to in your test is just a copy of the object being manipulated by the application so it is not going to be automatically refreshed. You need to fetch it from the database a second time to get the updated values.
Related
I am using Rails 5.2, Ruby 2.4.1, and Rspec. I am trying to make an automation test using Rspec to update a User's password. I know the code works from manually testing.
The RSpec test does not pass the #user.update_attributes(user_params) condition in the controller and then goes to the else condition. Thus, my RSpec test says that the passwords are still equal to each other. How can I get my RSpec test to pass the condition?
Here is the method
# Creates user, saves old password, generates a url for user to go to, updates password, reloads user, and test if old password equals new password
context "with a valid token" do
it "updates the user's password and resets the token" do
test_users = User.create(first_name: 'chase', last_name: 'dougherty', email: 'chase#gmail.com', password: '1', password_confirmation: '1')
old_password = test_users.password
test_users.generate_password_reset_token!
patch :update, params: { id: test_users.password_reset_token, user: { password: 'newpassword', password_confirmation: 'newpassword' } }
test_users.reload
expect(test_users.password).to_not eq(old_password)
end
end
Here is the Controller
# Finds user, test if update_attributes is true, updates password, logs user in, redirects user, displays flash
def update
#user = User.find_by(password_reset_token: params[:id])
if #user && #user.update_attributes(user_params)
#user.update_attribute(:password_reset_token, nil)
session[:user_id] = #user.id
redirect_to '/maps'
flash[:notice] = "Password updated"
else
flash[:notice] = "Password reset failure."
render action: 'edit'
end
end
private
def user_params
params.require(:user).permit(:password, :password_confirmation)
end
If you use has_secure_password method in your user model, you can use this syntax for check password has been changed:
it "changes user's password" do
expect { send_request }.to change { user.reload.authenticate(password) }.from(false).to(user)
end
If the update_attributes line is failing, you probably have some validations on the User model that are firing and causing it to return false.
Also, I would advise against "creating" a user object in the test files. Especially because the first time you run the tests it will create the entry, but then every time after that it will return a User instance that is not saved to the database (because of uniqueness violations in the data) and the tests may not run as expected.
Unless you want to clean up your created user objects after the test runs. Otherwise you will want to use factory_bot and stub out most of these models and the database calls.
Hi I am new to rspec (and unit testing in general) and want to test the following method:
class HelloController < ApplicationController
def hello_world
user = User.find(4)
#subscription = 10.00
render :text => "Done."
end
end
I am trying to use Rspec like so:
Describe HelloController, :type => :controller do
describe "get hello_world" do
it "should render the text 'done'" do
get :hello_world
expect(response.body).to include_text("Done.")
end
end
end
I would like to simply test that the method works properly and renders the test "done". I get the following error when I run the test:
Failure/Error: user = User.find(4)
ActiveRecord::RecordNotFound:
Couldn't find User with 'id'=4
But how do I properly create a user with that id before executing it? I have tried the following based on other tutorials and questions but it doesn't work:
describe "get hello_world" do
let(:user) {User.create(id: 4)}
it "should render the text 'done'" do
get :hello_world
expect(response.body).to include_text("Done.")
end
end
Thank you in advance.
Hey so really no action (e.g. def hello_world) should rely on a specific id. So a simple alternative could be to use user = User.last or to find the user by name user = User.find_by(name: "name"). Then in the test you would create any user if you using User.last in the action.
describe "get hello_world" do
let(:user) {User.create!}
it "should render the text 'done'" do
get :hello_world
expect(response.body).to include_text("Done.")
end
end
or if you are searching by name you can make a user with that name;
describe "get hello_world" do
let(:user) {User.create!(name: "name")}
it "should render the text 'done'" do
get :hello_world
expect(response.body).to include_text("Done.")
end
end
Hope this helps, questions welcome.
Do you really mean to use 'user = User.find(4)'? If you really meant to do that, you should stub the User's find method and return a user object.
it "should render the text 'done'" do
u = User.new #a new user, your test database is empty, so there's no user with id 4
User.stub(find: u) #stub the User's find method to return that new user
get :hello_world
expect(response.body).to include_text("Done.")
end
Another option is to send the user_id via params
it "should render the text 'done'" do
u = User.create(.... your user params)
get :hello_world, user_id: u.id
expect(response.body).to include_text("Done.")
end
and
def hello_world
user = User.find(params[:user_id])
#subscription = 10.00
render :text => "Done."
end
Anyway, I don't think you should be doing that, a hardcoded id is a bad sign. If you need to control users registrations and logins you can use something like Devise, and you may need to create an login a user before the spec.
I'm implementing a lazy login feature. My cucumber feature should describe it:
Feature: User log in
Scenario: Lazy login
Given I didn't log out the last time I was on the site
When I go to the homepage
Then I should automatically be logged in
And these are my step definitions:
Given(/^I didn't log out the last time I was on the site$/) do
user = FactoryGirl.create(:user)
visit new_user_session_path
fill_in('user[email]', with: user.email)
fill_in('user[password]', with: 'test123')
click_button('Sign in')
Capybara.reset_sessions!
end
When(/^I go to the homepage$/) do
visit root_path
end
Then(/^I should automatically be logged in$/) do #<-- Fails here
page.should have_content("Logout")
end
This is what happens when a user logs in: the cookies.signed[:auth_token] gets set. This will be used by a before filter in my ApplicationController so that users who open a fresh browser will be logged in automatically:
class SessionsController < Devise::SessionsController
def create
super
if user_signed_in?
puts 'yesssssss'
session[:user_id] = current_user.id
current_user.remember_me! if current_user.remember_token.blank?
cookies.signed[:auth_token] = {
:value => current_user.remember_token,
:domain => "mysite.com",
:secure => !(Rails.env.test? || Rails.env.development?)
}
puts "current_user.remember_token = #{current_user.remember_token}"
puts 'cookies:'
puts cookies.signed[:auth_token]
end
end
end
This is the before filter in my ApplicationController:
def sign_in_through_cookie
logger.info "logging in by cookie"
puts "logging in by cookie"
puts cookies.signed[:auth_token] #<-- PROBLEM: this returns nil.
return true if !current_user.nil?
if !cookies[:auth_token].nil? && cookies[:auth_token] != ''
user = User.find_by_remember_token(cookies.signed[:auth_token])
return false if user.blank?
sign_in(user)
puts 'success'
return true
else
return false
end
end
So the issue is that in the last step of my cucumber feature, cookies.signed[:auth_token] returns nil. I'm guessing this is just a capybara thing. So do I actually have to set a cookie in the test as opposed to using the one in my controller?
So eventually I figured it out after trying a lot of different things.
Given(/^I didn't log out the last time I was on the site$/) do
user = FactoryGirl.create(:user)
visit new_user_session_path
fill_in('user[email]', with: user.email)
fill_in('user[password]', with: 'test123')
click_button('Sign in')
Capybara.current_session.driver.request.cookies.[]('auth_token').should_not be_nil
auth_token_value = Capybara.current_session.driver.request.cookies.[]('auth_token')
Capybara.reset_sessions!
page.driver.browser.set_cookie("auth_token=#{auth_token_value}")
end
When(/^I go to the homepage$/) do
visit root_path
end
Then(/^I should automatically be logged in$/) do
page.should have_content("Logout")
end
UPDATE:
Here's what I use in case I'm using Selenium for some of the tests:
if Capybara.current_session.driver.class == Capybara::Selenium::Driver
auth_token = page.driver.browser.manage.cookie_named('auth_token')[:value]
page.driver.browser.manage.delete_all_cookies
page.driver.browser.manage.add_cookie(:name => "auth_token", :value => auth_token)
else
puts "cookies = #{Capybara.current_session.driver.request.cookies}"
Capybara.current_session.driver.request.cookies.[]('auth_token').should_not be_nil
auth_token_value = Capybara.current_session.driver.request.cookies.[]('auth_token')
Capybara.reset_sessions!
page.driver.browser.set_cookie("auth_token=#{auth_token_value}")
end
Use https://github.com/nruth/show_me_the_cookies which wraps the driver methods. It has methods for getting cookies, deleting cookies, and a method for creating cookies called create_cookie.
I needed just to test the cookie values
Inspiration taken from https://collectiveidea.com/blog/archives/2012/01/05/capybara-cucumber-and-how-the-cookie-crumbles
and ported to Rails 5.x
Create features/support/cookies.rb
With content
module Capybara
class Session
def cookies
#cookies ||= ActionDispatch::Request.new(Rails.application.env_config.deep_dup).cookie_jar
end
end
end
Before do
allow_any_instance_of(ActionDispatch::Request).to receive(:cookie_jar).and_return(page.cookies)
allow_any_instance_of(ActionDispatch::Request).to receive(:cookies).and_return(page.cookies)
end
Then the step for testing
Then('is set cookie {string} with value {string}') do |cookie, value|
expect(page.cookies.signed[cookie]).to eq value
end
I want to write controller test related password update test. I find authenticated person in the controller's first line with where condition. How can I write test related this line. I couldn't yiled any idea.
ChangePasswordsController
def update
person = Person.where(_id: session[:user_id]).first
identity = Identity.where(_id: person.user_id).first
unless params[:new_password] != params[:new_password_confirmation]
identity.password = params[:new_password].to_s
identity.password_confirmation = params[:new_password].to_s
identity.save
redirect_to root_url, :notice => "Password has been changed." + person.user_id
else
redirect_to :back, :alert => "Password & password confirmation are not match"
end
end
ChangePasswordsController Test
describe ChangePasswordsController do
setup do
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:identity]
#auth=request.env["omniauth.auth"]
end
it "should have edit action" do
get :edit
assert_response :success
end
it "should find person" do
...
end
it "should find identity" do
...
end
end
I don't think you should write a test whether the where finds a person or not. In this context you'd better just check whether the update method calls the find method on Person with the given parameter. Use something like this:
Person.should_receive(:find).with(id: <the userid you've setup for the test>) {[Person.new]}
It should be able to also check first but I can't remember, something to do with chainstubbing.
--preface: ignore if you want.
I'm new to rails, and working on a project that will require user authentication.
I found this tutorial and have been trying to go through it and understand what's happening. Of course, it's not exactly what I need as-is, so I've been modifying as I go along. The tutorial is also out of date in some areas, so of course I've had to update my code. So part of my problem is that I'm not sure if the bug is in my modifications, or some function that's been deprecated, or what.
--the question
This is the (simplest) test that fails. (" expected to not be nil" on the first assert statement.)
def test_authentication
#check we can log in
post :login, :user => { :username => "bob", :password => "test" }
assert_not_nil session[:user_id]
assert_equal users(:bob).id, session[:user_id]
assert_response :redirect
assert_redirected_to :action => 'welcome'
end
It calls the user_controller action login:
def login
if request.post?
if session[:user_id] = User.authenticate(params[:user][:username], params[:user][:password])
flash[:message] = "Login succeeded!"
redirect_to_stored
else
flash[:warning] = "Login failed."
end
end
end
which calls the User method authenticate. I know that authenticate works properly, however, because I have a single test that does pass:
def test_registration
#check that we can register and are logged in automatically
post :register, :user => { :username => "newuser", :password => "pass", :password_confirmation => "pass", :email => "newuser#web.com" }
assert_response :redirect
assert_not_nil session[:user_id]
assert_redirected_to :action => 'welcome'
end
which calls the user_controller action register
def register
#user = User.new(params[:user])
if request.post?
if #user.save
session[:user_id] = User.authenticate(#user.username, #user.password)
flash[:message] = "Registration succeeded"
redirect_to :action => 'welcome'
end
else
flash[:warning] = "Registration failed"
end
end
which successfully calls authenticate.
the users fixture has one relevant record:
bob:
username: bob
email: bob#mcbob.com
hashed_password: 77a0d943cdbace52716a9ef9fae12e45e2788d39 # test
salt: 1000
I've tested the hashed password and salt - "test" is the correct password.
So by my analysis, the bug has got to be in one of 3 places:
how I'm sending my post request,
how I'm accessing the parameters in the login action,
or some aspect of the fixture loading.
(originally I was using the tutorial's code to load the fixture explicitly (self.use_instantiated_fixtures = true; fixtures :users), but I read that all fixtures are automatically loaded before testing, so I took it out. That didn't change a thing.)
Of course, since I can't seem to find the problem in those areas, it could just as well be anywhere else.
Is it possible that there's a filter that is preventing your action getting called? If there's a general :before_filter => 'login_required' then you might not be reaching your login functionality at all. (Though admittedly the register action would have to be excluded for that test to pass)
In cases like this it's useful to stick some logging in (or run through a debugger) to see whether you even get to the part of the method that you think is failing. If it were me I'd stick a logger.debug("...") as the first line of the login method and then another after the check for request.post? and then another after the authentication check.