Reputation: 391
In my controller, I have a method called update_password, this method updates the user password after validating token. But, my tests are failing.What might be the problem?
In my controller,
def update_password
user = User.find_by(email: params[:email])
if user.nil?
render json: { error: 'Could not update' }, status: 422
else
user.update(user_params)
render json: { message: 'Successfully Updated' }, status: 200
end
end
def user_params
params.permit(:password, :password_confirmation, :current_password, :email)
end
Test:
describe 'update password for valid token' do
it'should update the password' do
user_params = {
password: 'newpassword',
password_confirmation: 'newpassword',
email: user.email
}
put '/api/v1/update_password', params: user_params
expect(user.password).to eq 'newpassword'
expect(user.reload.password_confirmation).to eq 'newpassword'
expect(user.reload.password).to eq(user.reload.password_confirmation)
json_response = JSON.parse(response.body)
expect(json_response['message']).to eq('Successfully Updated')
end
end
Factories:
FactoryBot.define do
factory :user do
sequence(:email) { |n| "user#{n}@example.com" }
password 'testcase'
username 'testcase'
password_confirmation 'testcase'
first_name 'testname'
last_name 'test'
end
end
Error I have got:
1) UsersRequests update password for valid token should update the password
Failure/Error: expect(user.password).to eq 'newpassword'
expected: "newpassword"
got: "testcase"
(compared using ==)
# ./spec/requests/users_requests_spec.rb:105:in `block (3 levels) in <top (required)>'
Finished in 0.35031 seconds (files took 5.69 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/requests/users_requests_spec.rb:98 # UsersRequests update password for valid token should update the password
Upvotes: 2
Views: 1169
Reputation: 102212
Your controller action is fundamentally broken. It returns the wrong response code when a record is not found (422 not 401) and returns 200 no matter if the record is updated or not. You are also letting the user update the email attribute!
It should look something like this:
def update_password
# this will raise ActiveRecord::NotFound if the record cannot be found
# this avoids duplication by relying on rescue_from to return 401 - not found
user = User.find_by!(email: params[:email])
# You need to check the return value to see if the record was updated!
if user.update(update_password_params)
# consider omitting the JSON payload as clients can just
# use the status code to determine if it was a success
render json: { message: 'Successfully Updated' }, status: 200
else
render json: { error: 'Could not update' }, status: 422
end
end
private
# this should be a subset of the params and not allow email!
def update_password_params
params.permit(:password, :password_confirmation, :current_password)
end
You can also do much better in the spec by using RSpec's change matchers:
describe 'update password for valid token' do
let!(:user) { create(:user) }
let(:user_params) do
{
password: 'newpassword',
password_confirmation: 'newpassword',
email: user.email
}
end
# don't start with 'should'
it 'updates the password' do
expect do
put '/api/v1/update_password', params: user_params
user.reload
end.to change(user, :password).to('newpassword')
end
it 'is successful' do
put '/api/v1/update_password', params: user_params
expect(response).to have_http_status 200
end
end
Your spec should simply test the intendended behavior - that the records password is updated.
Testing the password_confirmation
is not possible since its a virtual attribute - and not needed here. You instead need to test in a separate spec that the password is not updated if they do not match:
describe 'update password with invalid attributes' do
let!(:user) { create(:user) }
let(:user_params) do
{
password: 'newpassword',
password_confirmation: 'newpasswordxx',
email: user.email
}
end
it 'does not update the password' do
expect do
put '/api/v1/update_password', params: user_params
user.reload
end.to_not change(user, :password)
end
it 'reponds with 422' do
put '/api/v1/update_password', params: user_params
expect(response).to have_http_status 422
end
end
Upvotes: 2