shreejana
shreejana

Reputation: 391

User password update rspec

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

Answers (1)

max
max

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

Related Questions