Rails + Rspec. Michael Hartl tutorial. Implementing sign_in - ruby-on-rails

I'm getting this error that could not find the function but I can't understand why. Here is my code:
session_helper.rb
def sign_in(user)
cookies.permanent.signed[:remember_token] = [user.id, user.salt]
current_user = user
end
...
...
session_controller.rb
def create
user = User.authenticate(params[:session][:email], params[:session][:password])
if user.nil?
flash.now[:error] = "Invalid email/password combination"
#title = "Sign in"
render 'new'
else
sign_in(user)
redirect_to user
end
end
sessions_controller_spec.rb
it "should sign the user in" do
post :create, session: #attr
expect(controller.current_user).to eq #user
expect(controller).to be_signed_in
end
it "should redirect to the user show page" do
post :create, session: #attr
expect(response).to redirect_to(user_path(#user))
end
Rspec
2) SessionsController success should redirect to the user show page
Failure/Error: post :create, session: #attr
NoMethodError:
undefined method `sign_in' for #<SessionsController:0x00000006409380>
If I put the function sign_in in the sessions_controller I don't get this error but if I keep the function in the helper my program just doesn't see the function. Why?

include SessionsHelper in ApplicationController or SessionsController.
Helper methods are not directly accessible in controllers. You need to include them.

You should include helper module. Add this to spec_helper.rb
RSpec.configure do |config|
...
config.include SessionHelper #add this line
...
end

Related

RSpec sessions controllers - user with invalid params without devise

I've got a simple message app to learn RSpec where one user can create message to another user (only logged users can write messages). I didn't used devise or FactoryBot, this app is as simple as possible just for rspec learning.
I wanted to run these tests for sessions controller, but the second one (when user has invalid params) gives me an error Expected response to be a <3XX: redirect>, but was a <200: OK> and I don't understand why since hours.
RSpec.describe SessionsController, type: :controller do
let(:create_user) { #user = User.create(username: 'John', password: 'test123') }
describe 'POST #create' do
context 'when user is logged in' do
it 'loads correct user details and redirect to the root path' do
create_user
post :create, params: { session: { username: #user.username, password: #user.password } }
expect(response).to redirect_to(root_path)
end
end
context 'when user has invalid params' do
before do
create_user
post :create, params: { session: { username: #user.username, password: 'somepass' } }
end
it 'render new action' do
expect(assigns(:user)).not_to eq create_user
expect(response).to redirect_to(action: 'new')
end
end
end
end
Sessions Controller
class SessionsController < ApplicationController
before_action :logged_in_redirect, only: %i[new create]
def new; end
def create
user = User.find_by(username: params[:session][:username])
if user && user.authenticate(params[:session][:password])
session[:user_id] = user.id
flash[:success] = 'You have successfully logged in'
redirect_to root_path
else
flash.now[:error] = 'There was something wrong with your login'
render 'new'
end
end
end
I'm not quite sure if line expect(assigns(:user)).not_to eq create_user is in line with convention but it doesn't matter for result.
In your test you expect redirect response:
expect(response).to redirect_to(action: 'new')
And in the controller you just render new template:
render 'new'
I think it's a good approach to render new, you should change your spec to expect this.
expect(response).to render_template(:new)

Rails Rspec - undefined method `session'

Im testing my Session Controller but Im getting this error, the log in feature works, I tested it on the browser but Im new to testing on Rspec and can't get this to work
Failure/Error: expect(response.session[:user_id]).to eq(#user_attr.id)
NoMethodError:
undefined method `session' for #<ActionController::TestResponse:0xd30df10>
# ./spec/controllers/sessions_controller_spec.rb:20:in `block (3 levels) in <top (required)>'
This is the code of my controller:
def new
#user = User.new
end
def create
#user = User.find_by(username: params[:user][:username])
if #user && #user.authenticate(params[:user][:password])
session[:user_id] = #user.id
redirect_to root_path
else
render :new
end
end
Rspec code:
require 'rails_helper'
RSpec.describe SessionsController, type: :controller do
describe "get Login page" do
it "returns http status" do
get :new
expect(response).to have_http_status(:success)
end
end
describe "session" do
before(:each) do
#user = FactoryGirl.create(:user)
#user_attr = FactoryGirl.attributes_for(:user)
end
it "gives session" do
request.session[:user_id] = nil
post :create, user: #user_attr
expect(response.session[:user_id]).to eq(#user_attr.id)
end
end
end
session is a variable that is available without the request/response context as shown in your example. If you want to manipulate it or check the values it contains, you can simply do something like this:
it "gives session" do
session[:user_id] = nil
post :create, user: #user_attr
expect(session[:user_id]).to eq(#user_attr.id)
end

Undefined method `cookie_jar' for nil:NilClass for sign_in Rspec helper

Rails: RSpec - undefined method `cookie_jar' for nil:NilClass my question differs because I have my specs in the spec/requests directory. I'm following along with the rails tutorial
4) User Pages authorization as wrong user submitting a GET request to the Users#show action
Failure/Error: before { sign_in user, no_capybara: true }
NoMethodError:
undefined method `cookie_jar' for nil:NilClass
# ./spec/support/utilities.rb:6:in `sign_in'
# ./spec/requests/user_pages_spec.rb:77:in `block (4 levels) in <top (required)>'
spec/support/utilities.rb
include ApplicationHelper
def sign_in(user, options={})
if options[:no_capybara]
remember_token = User.new_remember_token
cookies[:remember_token] = remember_token
user.update_attribute(:remember_token, User.encrypt(remember_token))
else
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
end
end
spec/requests/user_pages_spec.rb
....
context "as wrong user" do
let(:user) { FactoryGirl.create(:user) }
let(:wrong_user) { FactoryGirl.create(:user, email: 'wrong#example.com') }
before { sign_in user, no_capybara: true }
describe "submitting a GET request to the Users#edit action" do
before { get edit_user_path(wrong_user) }
specify { expect(response.body).not_to match('Edit user') }
specify { expect(response).to redirect_to(root_path) }
end
describe "submitting a PATCH request to the Users#update action" do
before { patch user_path(wrong_user) }
specify { expect(response).to redirect_to(root_url) }
end
describe "submitting a GET request to the Users#show action" do
before {get user_path(wrong_user) }
specify {expect(response.body).not_to match(user.name) }
specify { expect(response).to redirect_to(root_path) }
end
end
SessionController
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:email].downcase)
if user && user.authenticate(params[:password])
session[:user] = user.id
sign_in user
redirect_to user
else
flash.now[:error] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
sign_out
redirect_to root_path
end
end
SessionsHelper
module SessionsHelper
def sign_in(user)
remember_token = User.new_remember_token
cookies.permanent[:remember_token] = remember_token
user.update_attribute(:remember_token, User.encrypt(remember_token))
self.current_user = user
end
def signed_in?
!current_user.nil?
end
def sign_out
current_user.update_attribute(:remember_token, User.encrypt(User.new_remember_token))
cookies.delete(:remember_token)
self.current_user = nil
end
def current_user=(user)
#current_user = user
end
def current_user
remember_token = User.encrypt(cookies[:remember_token])
#current_user ||= User.find_by(remember_token: remember_token)
end
end
So, the reason why this happens is that cookies just calls request.cookie_jar. It follows that the problem is that request is nil
Either mock request on the controller under test, or use something that will simulate a request into the controller.

Rails Tutorial Ch. 9 Exercise 6: Expected response to be a <redirect>, but was <200>

I am trying to write tests and application code to redirect users who are already signed-in to the root_path if they try to CREATE a user or visit the NEW user path.
Here are the tests I have written in user_pages_spec.rb:
describe "for signed in users" do
let(:user) { FactoryGirl.create(:user) }
before { sign_in user }
describe "using a 'new' action" do
before { get new_user_path }
specify { response.should redirect_to(root_path) }
end
describe "using a 'create' action" do
before { post users_path }
specify { response.should redirect_to(root_path) }
end
end
UsersController:
class UsersController < ApplicationController
before_action :unsigned_in_user, only: [:create, :new]
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
sign_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
private
# Before filters
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
def unsigned_in_user
puts signed_in?
redirect_to root_url, notice: "You are already signed in." unless !signed_in?
end
end
The puts signed_in? returns false. I am assuming this is the problem because I would expect it to return true. Here are the errors after running the tests using rspec. Any help is appreciated.
Failures:
1) User pages for signed in users using a 'create' action
Failure/Error: before { post users_path }
ActionController::ParameterMissing:
param not found: user
# ./app/controllers/users_controller.rb:52:in `user_params'
# ./app/controllers/users_controller.rb:20:in `create'
# ./spec/requests/user_pages_spec.rb:162:in `block (4 levels) in <top (required)>'
2) User pages for signed in users using a 'new' action
Failure/Error: specify { response.should redirect_to(root_path) }
Expected response to be a <redirect>, but was <200>
# ./spec/requests/user_pages_spec.rb:158:in `block (4 levels) in <top (required)>'
Within the sessions_helper.rb file:
def signed_in?
!current_user.nil?
end
In spec/support/utilities.rb:
def sign_in(user, options={})
if options[:no_capybara]
# Sign in when not using Capybara.
remember_token = User.new_remember_token
cookies[:remember_token] = remember_token
user.update_attribute(:remember_token, User.encrypt(remember_token))
else
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
end
end
Were you able to get your tests to pass?
In case you weren't, I had the same problem as you today, and was able to get the tests to pass by making two changes to the tests - passing a user hash when POSTing, and using the no_capybara option on the sign_in method, since get and post are not capybara methods and I think RSpec doesn't behave as we might expect if we switch from capybara to non-capybara methods within the same test.
describe "for signed-in users" do
let(:user) { FactoryGirl.create(:user) }
before { sign_in user, no_capybara: true }
describe "using a 'new' action" do
before { get new_user_path }
specify { response.should redirect_to(root_path) }
end
describe "using a 'create' action" do
before do
#user_new = {name: "Example User",
email: "user#example.com",
password: "foobar",
password_confirmation: "foobar"}
post users_path, user: #user_new
end
specify { response.should redirect_to(root_path) }
end
end
Same answer as najwa, but I used the FactoryGirl user with the Rails attributes method to avoid duplication:
describe "using a 'create' action" do
before { post users_path, user: user.attributes }
specify { response.should redirect_to(root_path) }
end
Helps to keep the data decoupled from the test code.

RSpec vs. Cookies - The test fails even if the applications works (simulating logged user)

I'm reading the Michael Hartl's "Ruby on Rails "Turorial" but I'm stuck here.
I wrote the tests to check user authorization on user edit/update, I've also wrote the controller code and it works:
If user is not logged and try to access users#edit or users#update it's redirected to the login page
If the user is logged and he try to edit another user is redirected to the root
The problem is in the spec that tests the point 2, I check that sending a PUT request to the user_path I'm redirected to the root but running rspec I get the following error:
Failures:
1) Authentication authorization as wrong user submitting a PUT request to users#update
Failure/Error: specify { response.should redirect_to(root_url) }
Expected response to be a redirect to <http://www.example.com/> but was a redirect to <http://www.example.com/signin>
# ./spec/requests/authentication_pages_spec.rb:73:in `block (5 levels) in <top (required)>'
It seems it's redirected to the signin_url instead of the root_url, like if the user is not logged at all...and this is what I guess cause the problem.
The application store a token in the cookies to authenticate the user.
To test this I wrote an helper method called signin_user(user) and put it in the spec/support/utilities.rb file:
# spec/support/utilities.rb
def sign_in(user)
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
# Make signin work even when not using capybara (e.g. when using get, or post)
cookies[:remember_token] = user.remember_token
end
As you can see the method set the remember_token cookie to make sure the user is logged even when using put, delete, etc...or at least it should work!
How can I make this spec pass as it should?
# spec/requests/authentication_pages_spec.rb
require 'spec_helper'
describe "Authentication" do
subject { page }
# ...
describe "authorization" do
# ...
describe "as wrong user" do
let(:user) { FactoryGirl.create(:user) }
let(:another_user) { FactoryGirl.create(:user, email: "another#example.com") }
before { sign_in(user) }
# ...
describe "submitting a PUT request to users#update" do
before { put user_path(another_user) }
specify { response.should redirect_to(root_url) }
end
end
end
end
UPDATE: And this is my UsersController:
class UsersController < ApplicationController
before_filter :signed_in_user, :only => [:edit, :update]
before_filter :correct_user, :only => [:edit, :update]
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
sign_in #user
flash[:success] = "Welcome to the sample App!"
redirect_to #user
else
render "new"
end
end
def edit
end
def update
if #user.update_attributes(params[:user])
sign_in(#user)
flash[:success] = "Your profile was successfully updated"
redirect_to #user
else
render "edit"
end
end
private
def signed_in_user
unless signed_in?
redirect_to signin_url, notice: "Please sign in to access this page."
end
end
def correct_user
#user = User.find(params[:id])
redirect_to root_url unless current_user?(#user)
end
end
After looking at the helpers from the rest of the code on your github repository I think I see the problem.
You're using the session variable for storing your :remember_token:
def user_from_remember_token
remember_token = session[:remember_token]
User.find_by_remember_token(remember_token) unless remember_token.nil?
end
The book uses cookies:
def user_from_remember_token
remember_token = cookies[:remember_token]
User.find_by_remember_token(remember_token) unless remember_token.nil?
end
Your test helper sets the cookie:
# Make signin work even when not using capybara (e.g. when using get, or post)
cookies[:remember_token] = user.remember_token
but because your user_from_remember_token is looking for session[:remember_token] not a cookie, it thinks the user isn't logged in so the test gets redirected to login_path instead of root_path.
It says why they use a cookie here: http://ruby.railstutorial.org/chapters/sign-in-sign-out?version=3.2#sec:a_working_sign_in_method
If you want to continue using a session you would need to adjust your test to sign in via post so the put has the session set, for example:
describe "submitting a PUT request to users#update" do
before do
post sessions_path, :email => user.email, :password => user.password
put user_path(another_user)
end
specify { response.should redirect_to(root_url) }
end
Thanks for the answer! I'm working through the book as well and have the same problem. The reason for using sessions is that exercise 2 in section 8.5 is to use session instead of cookies.
My answer is a little different. My SessionsController uses session variables in the params:
def create
user = User.find_by_email(params[:session][:email])
if user && user.authenticate(params[:session][:password])
sign_in user
redirect_back_or user
else
flash.now[:error] = 'Invalid email/password combination'
render 'new'
end
end
so the fix is a slightly more complicated:
#spec/support/utilities.rb
def sign_in(user)
visit signin_path
fill_in "session_email", with: user.email
fill_in "session_password", with: user.password
click_button "Sign in"
# Sign in when not using Capybara as well.
post sessions_path, {:session => { :email => user.email, :password => user.password }}
end
I had the same issue but a different cause.
I had a typo in spec/support/utilities.rb
def sign_in(user)
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
# Sign in when not using Capybara as well
cookies[:remember_toke] = user.remember_token
end
Note that the last line says ":remember_toke" instead of ":remember_token"
Somehow that caused the test to fail and redirected to signon.
So I am documenting this here in case someone else makes the same type of mistake.

Resources