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 to log in or sign up users, this app is as simple as possible just for rspec learning.
I want to test create method in 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
def logged_in_redirect
if logged_in?
flash[:error] = 'You are already logged in'
redirect_to root_path
end
end
This is relevant code from my sessions controller spec:
RSpec.describe SessionsController, type: :controller do
describe 'POST #create' do
context 'when password is correct' do
let(:user) { User.create(username: 'John', password: 'test123') }
it 'redirect to root path' do
post :create, session: { username: 'John', password: 'test123' }
expect(response).to have_http_status(:redirect)
end
end
end
end
The error says undefined method `[]' for nil:NilClass. It works as expected in the application. What am I missing? Should I use session[:user_id] after let(:user) ?
Use let to define a memoized helper method. The value will be cached
across multiple calls in the same example but not across examples.
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
- List item
method's invocation before each example.
xou can refer this to get further detailsrelish docs
Possibility 1
RSpec.describe SessionsController, type: :controller do
describe 'POST #create' do
before :each do
#user = User.create(username: 'John', password: 'test123')
end
context 'when password is correct' do
it 'redirect to root path' do
post :create, params: {session: { username: #user.username, password: #user.password }}
expect(response).to have_http_status(:redirect)
end
end
end
end
Possibility 2
RSpec.describe SessionsController, type: :controller do
let(:create_user) do
#user = User.create(username: 'John', password: 'test123')
end
describe 'POST #create' do
context 'when password is correct' do
it 'redirect to root path' do
create_user
post :create, params: {session: { username: #user.username, password: #user.password }}
expect(response).to have_http_status(:redirect)
end
end
end
end
Related
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)
I am trying to write spec code for my controller it gets failed. And i am not sure where it gets failed.
Controller Code
def index
#users = User.all
end
def update
authorize! :update, #user
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to user_index_path }
else
format.html { render :index }
end
end
end
private
def set_user
#user = User.find(params[:id])
end
def user_params
params.permit(:active)
end
Spec Code for the above controller
RSpec.describe UserController, type: :controller do
describe 'GET #index' do
let(:user) {User.create!(name: "hari")}
context 'with user details'do
it 'loads correct user details' do
get :index
expect(response).to permit(:user)
end
end
context 'without user details' do
it 'doesnot loads correct user details' do
get :index
expect(response).not_to permit(:user)
end
end
end
describe 'Patch #update' do
context 'when valid params' do
let(:attr) do
{active: 'true'}
end
before(:each) do
#user = subject.current_user
put :update, params: { user: attr }
#user.reload
end
it 'redirects to user_index_path ' do
expect(response).redirect_to(user_index_path)
end
it 'sets active state' do
expect(#user.active?('true')).to be true
end
end
context 'when invalid param' do
let(:attr) do
{active: 'nil'}
end
before(:each) do
#user = subject.current_user
put :update, params: { user: attr }
#user.reload
end
it 'render index' do
expect(respone.status).to eq(200)
end
it 'doesnot change active state' do
expect(#user.active?(nil)).to be true
end
end
end
end
I am just a beginner and tried the spec code for my controller by checking https://relishapp.com/rspec/rspec-rails/docs/gettingstarted. Can you help me where my spec goes wrong or could anyone give me a few test examples for these methods or could redirect me to an rspec guide? the index method is getting failed
and my
terminal log is
1) UserController GET #index with user details loads correct user details
Failure/Error: expect(response).to permit(:user)
NoMethodError:
undefined method `permit' for #<RSpec::ExampleGroups::UserController::GETIndex::WithUserDetails:0x00005614152406b0>
Did you mean? print
# ./spec/controllers/user_controller_spec.rb:10:in `block (4 levels) in <top (required)>'
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
I am trying to test a controller create method in a rails app using RSpec as shown below:
def create
#user = User.new(user_params)
if #user.save
redirect_to user_path(#user.id)
else
render new_user_path
flash[:error] = "User not saved"
end
end
However if i stub out .new to prevent the test from using Active Record and the User model by forcing it to return true the id of the #user is not set by .save as normal so I cannot test for it redirecting to user_path(#user.id) as #user.id is nil
Here is my initial test for RSpec:
it "creates a user and redirects" do
expect_any_instance_of(User).to receive(:save).and_return(true)
post :create, { user: {name: "John", username: "Johnny98", email: "johnny98#example.com"} }
expect(assigns(:user).name).to eq("John")
expect(response).to redirect_to user_path(assigns(:user))
end
How should I test for this redirect in RSpec.
You should use mocks - https://www.relishapp.com/rspec/rspec-mocks/docs.
user = double("user", id: 1, save: true)
Then you should mock you method with double you've just created
expect(User).to receive(:new).and_return(user)
And then test redirect.
expect(response).to redirect_to user_path(user)
I hope this will help.
I would do it in this way:
it 'should redirect to a user if save returned true' do
#user_instance = double
#user_id = double
allow(User).to receive(:new).and_return(#user_instance)
allow(#user_instance).to receive(:save).and_return(true)
allow(#user_instance).to receive(:id).and_return(#user_id)
post :create, {:user => valid_attributes}
expect(response).to redirect_to(user_path(#user_id))
end
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.