Reputation: 1826
I've put together a basic application with user authentication using bcrypt-ruby
and has_secure_password
. The result is essentially a barebones version of the application from the Rails Tutorial. In other words, I have a RESTful user model as well as sign-in and sign-out functionality.
As part of the tests for editing a user's information, I've written a test for changing a password. Whereas changing the password works just fine in the browser, my test below is not passing.
subject { page }
describe "successful password change"
let(:new_password) { "foobaz" }
before do
fill_in "Password", with: new_password
fill_in "Password Confirmation", with: new_password
click_button "Save changes"
end
specify { user.reload.password.should == new_password }
end
Clearly, I'm misunderstanding some basic detail here.
In short:
1) Why exactly is the code above not working? The change-password functionality works in the browser. Meanwhile, rspec
continues to reload the old password in the last line above. And then the test fails.
2) What is the better way to test the password change?
Edit:
With the initial password set to foobar
, the error message is:
Failure/Error: specify { user.reload.password.should == new_password }
expected: "foobaz"
got: "foobar" (using ==)
Basically, it looks like the before
block is not actually saving the new password.
For reference, the related controller action is as follows:
def update
@user = User.find(params[:id])
if @user.update_attributes(params[:user])
flash[:success] = "Profile Updated"
sign_in @user
redirect_to root_path
else
render 'edit'
end
end
Upvotes: 2
Views: 3720
Reputation: 9958
For Devise users, use #valid_password?
instead:
expect(user.valid_password?('correct_password')).to be(true)
Credit: Ryan Bigg
Upvotes: 9
Reputation: 7540
Your answer (using authenticate
) is the right approach; you should be satisfied with it. You want to compare the hashed versions of the passwords not the @password (via attr_accessor) in the model. Remember that you're saving a hash and not the actual password.
Your user
in your test is an copy of that user in memory. When you run the tests the update method loads a different copy of that user in memory and updates its password hash which is saved to the db. Your copy is unchanged; which is why you thought to reload to get the updated data from the database.
The password field isn't stored in the db, it's stored as a hash instead, so the new hash gets reloaded from the db, but you were comparing the ephemeral state of @password in your user
instance instead of the the encrypted_password.
Upvotes: 2
Reputation: 1826
One not so satisfying solution here is to write a test using the #authenticate
method provided by bcrypt-ruby
.
specify { user.reload.authenticate(new_password).should be_true }
Granted this isn't a proper integration test, but it will get us to green.
Upvotes: 2