I'm creating a Ruby on Rails application, and am trying to run a test involving my User model to see if the "Remember me" feature works. I'm using Rails' inbuilt cookies hash to store the cookies, and the session hash to store the current session. I've run various tests (integration, model, and controller) where I use the session variable, but for some reason in this particular case it isn't being recognized.
NameError: undefined local variable or method `session' for #<UserTest:0x0000000658b5c8>
The error happens in the else block in the log_in_as method below:
test_helper.rb
...
def log_in_as(user, options = {})
password = options[:password] || 'password'
remember_me = options[:remember_me] || '1'
if integration_test?
post login_path, session: { email: user.email, password: password, remember_me: remember_me }
else
session[:user_id] = user.id
end
end
I call log_in_as in my User test; both of these tests fail.
user_test.rb
require 'test_helper'
...
test "login with remembering" do
log_in_as(#user, remember_me: '1')
assert_not_nil cookies['remember_token']
end
test "login without remembering" do
log_in_as(#user, remember_me: '0')
assert_nil cookies['remember_token']
end
...
And when I remove that line of code from the helper, an error is thrown saying that cookies isn't recognized. What is the issue here?
The session hash isn't available in models, only in controllers and views and controller and view tests.
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'm working my way through Chapter 9 of https://www.railstutorial.org/ and am running into trouble with an integration test.
The test looks like this:
class UsersLoginTest < ActionDispatch::IntegrationTest
...
test "login with remembering" do
log_in_as(#user, remember_me: '1')
assert_not_empty cookies['remember_token']
end
...
end
The call to log_in_as is a function added to ActionDispatch::IntegrationTest in my test_helper, and appears to be working as expected. One of the side effects of this function is that a cookie named 'remember_token' gets set. However, the assertion fails with the message:
FAIL["test_login_with_remembering", UsersLoginTest, 14.115229932998773]
test_login_with_remembering#UsersLoginTest (14.12s)
Expected nil (NilClass) to respond to #empty?.
test/integration/users_login_test.rb:48:in `block in <class:UsersLoginTest>'
Any idea what's going on here? Running the test in the debugger seems to suggest that the cookies object doesn't contain anything that looks like I would expect (there are a couple of cookies that I'm setting in the app, and none of them are appearing). All google turns up is a bunch of people suggesting that it's a bad idea to access cookies in integration tests. I'm running Rails 5.1.2 if that helps.
Rails earlier versions change the behavior, the Rails tutorial seems to be deprecated due to an older version Rails.
To pass tests you can forget helper like:
class UsersLoginTest < ActionDispatch::IntegrationTest
...
test "remember if checked in login" do
post login_path, params: { session: { email: #user.email, password: "password", remember_me: '1'}}
assert_not_empty cookies[:remember_token]
end
test "no remember if not checked in login" do
post login_path, params: { session: { email: #user.email, password: "password", remember_me: '1'}}
post login_path, params: { session: { email: #user.email, password: "password", remember_me: '0'}}
assert_empty cookies[:remember_token]
end
...
end
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 recently installed Devise in my app to handle auth, replacing the auth system from Michael Hartl's tutorial. It's working fine in the app itself, but I can't get my tests to auth properly, so they're pretty much all failing, which makes me sad. I'm using Rails 4 with Minitest.
Here's an example of one of my controller tests that fails:
learning_resources_controller_test.rb
require 'test_helper'
class LearningResourcesControllerTest < ActionController::TestCase
def setup
#user = users(:testuser1)
end
test "user can submit new resource" do
sign_in #user # Devise helper
post :create, {:learning_resource => {:name => "My resource"}}
resource = assigns(:learning_resource)
assert_redirected_to topic_path(#topic1, :learning_resource_created => "true")
end
end
test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
class ActiveSupport::TestCase
fixtures :all
# Return true is test user is signed in
def is_signed_in?
!session[:user_id].nil?
end
def sign_in_as(user, options = {})
password = options[:password] || 'password'
remember_me = options[:remember_me] || '1'
if integration_test?
# Sign in by posting to the sessions path
post signin_path, session: { email: user.email,
password: password,
remember_me: remember_me }
else
# Sign in using the session
session[:user_id] = user.id
end
end
private
def integration_test?
defined?(post_via_redirect)
end
end
class ActionController::TestCase
include Devise::TestHelpers
end
fixtures/users.yml
testuser1:
name: Test User 1
email: testuser1#mydumbdomain.com
password_digest: <%= User.digest('password') %>
The assert_redirected_to in the test always fails as the redirect is the sign in page instead of the topic page. All of my other tests fail in similar ways, indicating the user isn't signed in. I have scoured the Devise wiki and docs, but most of them cover testing with Rspec, not Minitest.
I tried using byebug within the test after the sign_in to check out the session, and I get this:
(byebug) session.inspect
{"warden.user.user.key"=>[[336453508], ""]}
If I try to call the :create, I get this error:
DEPRECATION WARNING: ActionDispatch::Response#to_ary no longer
performs implicit conversion to an array. Please use response.to_a
instead, or a splat like status, headers, body = *response. (called
from puts at
/Users/me/.rbenv/versions/2.2.2/lib/ruby/2.2.0/forwardable.rb:183)
302 {"X-Frame-Options"=>"SAMEORIGIN", "X-XSS-Protection"=>"1;
mode=block", "X-Content-Type-Options"=>"nosniff",
"Location"=>"http://test.host/signup",
"Set-Cookie"=>"request_method=POST; path=/",
"Content-Type"=>"text/html; charset=utf-8"}
Any ideas what I'm missing here?
The error is with hash
post :create, {:learning_resource => {:name => "My resource"}}
Try
post :create, :learning_resource => {:name => "My resource"}
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 [].