Per the title, I'm setting session[:user_id] in my controller if a user is authenticated successfully and then am testing that the session value matches the user id, but the session is nil with the following setup:
sessions_controlller.rb:
class SessionsController < ApplicationController
def create
user = User.find_by username: params[:username]
if user.try(:authenticate, params[:password])
session[:user_id] = user.id
redirect_to user_posts_path, notice: "Successfully logged in!"
else
redirect_to new_session_path, alert: "Invalid credentials."
end
end
end
session_controller_test.rb:
test "should sign in user with correct credentials" do
user_to_log_in = users(:one)
post :create, { password: "password", username: "yes" }
assert_equal user_to_log_in.id, session[:user_id]
end
user.rb:
class User < ActiveRecord::Base
has_secure_password
end
users.yml:
one:
id: 1
username: yes
password_digest: <%= BCrypt::Password.create('password', cost: 4) %>
How do I write a passing test here?
You may not necessarily have access to server data, like session, in your tests. Check out rack_session_access https://github.com/railsware/rack_session_access for a possible answer.
Hah, so apparently, with a username of 'yes' in users.yaml, the record in the test DB gets saved with a username of 't', so I assume it tries to convert it to some form of 'true'... Anyway changing the username in the fixture fixed the problem.
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.
I am trying to test that someone is able to login to my site by making a POST request to my SessionsController. I've seen this way recommended in a few places:
it 'must be able to sign in a user' do
user = create(:user)
post :create, format: :js, user: {email: user.email, password: user.password, remember_me: 0}
assert_response :success
#controller.current_user.must_equal user
end
But this test is not correct. Calling #controller.current_user will attempt to authenticate the user using the posted parameters and will return user if the supplied email/password is correct. There is no guarantee that the create action is actually calling sign_in or current_user.
Even if I re-write the test to check that these methods are called, it's possible that other methods could be called e.g. sign_out.
Is there a more definitive way to ultimately check if a user is logged in, and if so, who the user is?
EDIT -
For example, the following test will pass
it 'must sign in a user' do
#controller.current_user.must_equal nil
post :create, format: :js, user: {email: #user.email, password: #user.password, remember_me: 0}
assert_response :success
#controller.current_user.must_equal #user
end
when the SessionsController#create action is:
def create
respond_to do |format|
format.js {
render nothing: true, status: 200
}
end
end
Solution with minimal changes to proposed code in the question:
You need to initialize the system before the test starts. Try prepending following code before your it 'must be able to sign in a user' do code:
before (:each) do
user = FactoryGirl.create(:user)
sign_out user
end
This should turn your test into a valid test for your post controller.
Explanation:
My assumption is, that your test above always succeeds, because the user is already signed in (by other tests run before this one). You could verify this by using byebug in the line after it and run current_user in bybug's console. If it is not nil, the user is already signed in, which is invalidating your test.
Note, that (different from what is discussed above in the comments), current_user does not change the status of the user; it is a read-only function.
Shorter/cleaner solution:
In my opinion, there is a a cleaner way to perform such a test like follows:
def sign_in_via_post(user)
post :create, format: :js, user: {email: user.email, password: user.password, remember_me: 0}
end
...
before (:each) do
user = FactoryGirl.create(:user)
sign_out user
end
it 'must be able to sign in a user' do
{ sign_in_via_post user }.should change { current_user }.from(nil).to(user)
end
With the should change from nil to user statement, you verify, that the user was logged out before the test begins and that the user is logged in, after the test has been performed.
Note, that the part
{ sign_in_via_post user }.should change { current_user }.from(nil).to(user)
is equivalent to the (maybe easier to understand) code
{ sign_in_via_post user }.should change { user_signed_in? }.from(false).to(true)
as discussed here.
I'm attempting to create some controller specs in my Rails 5 app using rspec, but the code keeps throwing the following error:
1) SessionsController Log in and log out logs in with valid user
Failure/Error: user = User.find_by(email: params[:session][:email].downcase)
NoMethodError:
undefined method `[]' for nil:NilClass
My spec is pretty straightforward. The user instance variable uses factory-girl to create a user with the email "user#example.com" and password as "password." When I call puts on these variables, I can see that they are set correctly.:
require 'rails_helper'
RSpec.describe SessionsController, type: :controller do
before :each do
#user = create(:user)
end
describe "Log in and log out" do
before :each do
post :create, { session: { email: #user.email,
password: #user.password }}
end
it "logs in with valid user" do
puts #user.email + " " + #user.password
expect(is_logged_in?).to be_truthy
end
end
end
Finally, the code from the sessions controller is below:
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
remember user
redirect_to user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
end
Am I misunderstanding the way params are sent to the controller in rspec? Is there any other reason for this error to be returned?
Changes were made in Rails 5 to the way you send params in controller tests.
Instead of:
before :each do
post :create, { session: { email: #user.email,
password: #user.password }}
end
You need to provide the params key in post request attribute hash. Like so...
before :each do
post :create, params: { session: { email: #user.email,
password: #user.password }}
end
It's subtle, but it's different. Let me know if that works.
I'm coding a Rails 4 application to learn Rails & testing. My program code works as expected, but I can't figure out why I'm getting a no method error when posting to the create method in a Sessions controller test (RSpec v. 3.1.0) Here's the text of the error:
Failure/Error: post :create, email: "testerx#tester-x.net", password: "passwordx"
NoMethodError:
undefined method `[]' for nil:NilClass
This is relevant code from my Sessions Controller spec:
describe "POST create" do
context "with correct credentials" do
let!(:user) { User.create(user_name: "Testerx", email: "testerx#tester-x.net", password: "passwordx", password_confirmation: "passwordx", workout_enthusiast: "true" ) }
it "redirects to user show page" do
post :create, email: "testerx#tester-x.net", password: "passwordx"
expect(response).to be_redirect
expect(response).to redirect_to(users_show_path)
end
This is my Sessions Controller code:
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
# Logs the user in and redirects to the user's show page.
log_in user
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
redirect_to user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
log_out if logged_in?
redirect_to root_url
end
end
The error says undefined method for nil:NilClass. I'm sure the user is valid. I can't figure out why posting to the create method is not working in the test scenario. It works as expected in the application. What am I missing?
Change post :create, email: "testerx#tester-x.net", password: "passwordx" to post :create, session: { email: "testerx#tester-x.net", password: "passwordx" }.
The second argument of post is a parameter hash which will be sent to the controller. You are now passing { email: "testerx#tester-x.net", password: "passwordx" } to post, and obviously there is no session key in the parameter hash. When your controller tries to access paramas[:session][:xxx], it gets NoMethodError because params[:session] is nil, and nil does not have method [].
I've been working on an app for learning purposes which includes OmniAuth for Facebook logins, Cucumber for BDD and CanCan & Rolify for permissions and roles. No Devise is used so far. I'm trying to write a test that involves logging a user with admin role and then visiting a restricted path. Also, users that have been created with OmniAuth have simple attributes: If the user has been confirmed to use the site, he/she will have confirmed: 1 and confirmation_token: nil; otherwise it will be confirmed: 0 and confirmation_token: . The idea actually works if I'm not in a Cucumber environment, but inside it, the page gives me a CanCan::AccessDenied error. Oh, and I've also set OmniAuth test mode, that works fine, env["omniauth.auth"] returns a proper mock hash.
This is my test (I'm still learning so bear with me)
#omniauth_test
Given /^I am logged as an admin$/ do
#ensure no session is active
visit '/signout'
#user = FactoryGirl.create(:user, confirmed: 1, confirmation_token: nil)
#user.add_role :admin
visit root_url
click_link "log_in" #means going to '/auth/facebook/callback'
end
And /^I check the user list$/ do
visit users_path #fails
end
This is my Factory for user, nothing complicated:
FactoryGirl.define do
factory :user do |u|
u.email 'test#example.com'
end
end
This is my SessionsController:
class SessionsController < ApplicationController
def create
reset_session
service = Service.from_omniauth(env["omniauth.auth"])
session[:user_id] = service.user.id
session[:service_id] = service.id
session[:expires_at] = 5.minutes.from_now
if service.user.confirmed == 0
redirect_to edit_user_path(service.user)
elsif service.user.confirmed == 1
if service.user.has_role? :member
redirect_to root_url
elsif service.user.has_role? :admin
redirect_to users_path
else
redirect_to root_url, :notice => "Work in progress!"
end
end
end
And finally, Service.rb:
class Service < ActiveRecord::Base
attr_accessible :user_id, :provider, :uid, :name, :token, :updated_at
validates_uniqueness_of :uid, :scope => [:provider]
belongs_to :user
def self.from_omniauth(auth)
where(auth.slice(:provider, :uid)).first_or_initialize.tap do |service|
if !service.user
user = User.create(:email => auth.info.email)
service.user = user
#for some reason, instance variable #user created by FactoryGirl is nil at this point (test env)
user.add_role :signedup
end
service.provider = auth.provider
service.uid = auth.uid
service.name = auth.info.name
service.token = auth.credentials.token
service.save!
end
end
What I would like is to somehow use the OmniAuth hash and add the admin role and the confirmed attributes only for test mode, without messing too much with the production code (if possible), maybe adding helper methods to env.rb, and logging in with that user.
Silly me. This did the trick:
#omniauth_test
Given /^I am logged as an admin$/ do
#user = FactoryGirl.create(:user, confirmed: 1, confirmation_token: nil)
#user.add_role :admin
#service = FactoryGirl.create(:service, user: #user, uid: '1234567', provider: 'facebook')
end