Lesson n°10

Password reset tests

Testing the new action

Once again, we'll write some tests for the feature we just implemented. The first action we'll test is the #new action to ensure that the password reset form is being rendered correctly.

require "test_helper"

class PasswordResetsControllerTest < ActionDispatch::IntegrationTest
  test "#new" do
    get new_password_reset_path
    assert_response :success
  end
end

If we run the tests, they should pass.

Testing the create action

Next, we'll test the #create action:

# test/controllers/password_resets_controller_test.rb

require "test_helper"

class PasswordResetsControllerTest < ActionDispatch::IntegrationTest
  # The previous test

  test "#create" do
    assert_emails 1 do
      post password_reset_path, params: { email: users(:alex).email }
    end

    assert_redirected_to root_path
  end
end

In this test, we ensure that an email is sent when an existing user submits the password reset form with their email. We also check that the user is redirected to the root path.

Testing the edit action

The #edit action renders the form to actually update the password. Here's the test for it:

# test/controllers/password_resets_controller_test.rb

require "test_helper"

class PasswordResetsControllerTest < ActionDispatch::IntegrationTest
  # The previous tests

  test "#edit" do
    get edit_password_reset_path
    assert_response :success
  end
end

If we run the tests once again, they should pass.

Testing the update action

The last action we need to test is the #update action. This action is responsible for updating the user's password. Here the tests will be more interesting because we will test all three possible cases.

Let's start with the happy path where a user submits a valid password with a valid password reset token:

# test/controllers/password_resets_controller_test.rb

require "test_helper"

class PasswordResetsControllerTest < ActionDispatch::IntegrationTest
  # The previous tests

  test "#update resets the password with a valid token and valid params" do
    user = users(:alex)
    token = user.generate_token_for(:password_reset)

    patch password_reset_path, params: {
      password_reset_token: token,
      password_reset: { password: "newpassword", password_confirmation: "newpassword" }
    }
    assert_redirected_to dashboard_path
    assert_signed_in
    assert user.reload.authenticate("newpassword")
  end
end

In this test, we retrieve Alex from the fixtures and generate a valid password reset token. We then simulate the form submission with valid params by sending a patch request with the password reset token and the new password in the params.

We then check that the user is redirected to the dashboard and was successfully signed in.

Finally, we check that the password has successfully been updated to "newpassword" by calling the authenticate method that will compare the password hashes.

Next, we'll test the case where the password reset token is invalid:

# test/controllers/password_resets_controller_test.rb

require "test_helper"

class PasswordResetsControllerTest < ActionDispatch::IntegrationTest
  # The previous tests

  test "#update fails without a valid token" do
    patch password_reset_path, params: {
      password_reset_token: "invalid",
      password_reset: { password: "newpassword", password_confirmation: "newpassword" }
    }
    assert_redirected_to new_password_reset_path
    follow_redirect!
    assert_select "main", /Invalid token/
  end
end

In this test, we check that when the password reset token is invalid, we are redirected to the new password reset form with an error message containing "Invalid token".

The last test we'll perform is when the password reset token is valid but the password and password confirmation don't match:

# test/controllers/password_resets_controller_test.rb

require "test_helper"

class PasswordResetsControllerTest < ActionDispatch::IntegrationTest
  # The previous tests

  test "#update fails if password and password confirmation don't match" do
    user = users(:alex)
    token = user.generate_token_for(:password_reset)

    patch password_reset_path, params: {
      password_reset_token: token,
      password_reset: { password: "newpassword", password_confirmation: "notmatching" }
    }
    assert_response :unprocessable_entity
    assert_select "main", /Password confirmation doesn't match Password/
  end
end

In this case, we once again generate a valid password reset token and simulate the form submission with a password and password confirmation that don't match. We then check that there is an explicit error message displayed on the page.

If we run the tests once again, they should all pass, and we will have successfully tested our password reset flow.