Search code examples
ruby-on-railscapybaraintegration-testingminitestclearance

How to test a password reset mailer with Capybara and Minitest in Rails 5


I'm using the Clearance gem in Rails along with Capybara and Minitest and I can't figure out how to test the password reset mailer. I'm not trying to test the Clearance gem, which is already well-tested, but I'd like a high-level integration test to ensure the expected user experience doesn't break, and that includes the mailer.

Here's the test that I can't complete (/integration/password_reset_test.rb):

require 'test_helper'

class PasswordResetTest < ActionDispatch::IntegrationTest

  def setup
    ActionMailer::Base.deliveries.clear
    @user   = create(:user)
  end

  test "User can reset their password" do
    visit "/login"
    assert page.current_path == "/login"
    click_link "Forgot password?"
    assert page.current_path == "/passwords/new"
    assert_selector "h1", text: "Reset your password"
    fill_in("Email", :with => @user.email)
    click_button "Reset your password"
    assert page.current_path == "/passwords"
    assert_selector "h1", text: "Your password reset email is on the way."
    # This is where I'm stuck
    # Would like to test that the correct email was sent by checking
    #      basic content in the email and then simulate a user clicking
    #      the password reset link and then updating their password.
  end
end

How do you actually test that the mail was sent properly and is there a way to simulate capybara clicking through the password reset link in the email and then filling out the form to reset the password?

I tried this as well, but this line failed, so I'm clearly not doing something right:

assert_equal 1, ActionMailer::Base.deliveries.size

I can test manually by grabbing the password reset email link from the server logs, so the feature is working properly.

All of the examples I can find online are assuming you're using Rspec, but nothing for Minitest. I also tried using the capybara-email gem, but that did not have Minitest examples and I couldn't get that working either.

For reference: Gemfile test_helper.rb


Solution

  • For what you want to do capybara-email is a good choice. To set it up you would include it either into ActionDispatch::IntegrationTest or into the individual test class where it's needed (in your current case - PasswordResetTest). You, most likely, also need to configure ActiveJob to perform jobs when they are queued rather than delaying them (otherwise the emails won't actually be sent). One way to do that is by including ActiveJob::TestHelper and then using the perform_enqueued_jobs method it provided. That leads to something along the lines of

    require 'test_helper'
    
    class PasswordResetTest < ActionDispatch::IntegrationTest
      include Capybara::Email::DSL
      include ActiveJob::TestHelper
    
      def setup
        clear_emails
        @user   = create(:user)
      end
    
      test "User can reset their password" do
        perform_enqueued_jobs do
          visit "/login"
          assert_current_path("/login")
          click_link "Forgot password?"
          assert_current_path("/passwords/new")
          assert_selector "h1", text: "Reset your password"
          fill_in("Email", :with => @user.email)
          click_button "Reset your password"
          assert_current_path("/passwords")
          assert_selector "h1", text: "Your password reset email is on the way."
        end
        open_email(@user.email)
        assert_content(current_email, 'blah blah')
        current_email.click_link('Reset Password')
        assert_current_path(reset_password_path)
        ...  # fill out form with new password, etc.
      end
    end
    

    Note the use of assert_current_path rather than assert page.current_path... - You generally want to prefer the former since the latter has no waiting/retrying behavior and can lead to flaky tests. Also note that writing tests that hardcode in the path names leads to a nightmare if you ever want to change the path names so you might be better off writing your codes using the rails provided routes helpers instead

    assert_current_path(login_path)
    

    etc.