Rails 6、minitest系统测试:测试密码重置失败,但手动更改密码后有效

Rails 6, minitest system test: password reset fails in tests, but works when I manually change it

我正在使用 Rails 6 和带有内置系统测试的 minitest(我认为它使用 Capybara)并使用 FactoryBot 生成我的测试记录。

我有一个非常标准的密码恢复功能,我正在尝试实现。

我已经证实,当我进入浏览器中的页面并填写信息时,它确实更改了用户密码,但由于某种原因,密码在测试中从未更改过。

这几乎就像 @user 对象在测试中被缓存并且不会 reload 在测试中,但我不知道为什么会这样。

有人知道为什么这个测试会失败,但当我手动更改密码时,该功能在 "real life" 中有效吗?

# test/system/password_resets_test.rb
require "application_system_test_case"

class PasswordResetsTest < ApplicationSystemTestCase
  test "change password" do
    original_password = "password"
    new_password = "new-password"
    @user = create(:user, password: original_password, password_reset_token_sent_at: Time.current)

    visit password_reset_path(@user.password_reset_token)
    fill_in "user[password]", with: new_password
    click_on "Update Password"

    assert_equal(@user.reload.password, new_password)
  end
end
# app/views/password_resets/show.html.erb
<%= form_with model: @user, url: password_reset_path(@user.password_reset_token), method: :put do |form| %>
  <div class="field">
    <%= form.label :password, "Password" %><br />
    <%= form.password_field :password, autofocus: true, required: true %>
  </div>
  <div class="field">
    <%= form.submit "Update Password" %>
  </div>
<% end %>
# app/controllers/password_resets_controller.rb
class PasswordResetsController < ApplicationController
  def show
    if @user = User.find_by(password_reset_token: params[:id])
      if @user.password_reset_token_expired?
        flash[:error] = "Your password reset has expired"
        redirect_to new_password_reset_path
      end
    else
      flash[:error] = "Invalid password reset token"
      redirect_to new_password_reset_path
    end
  end

  def update
    @user = User.find_by(password_reset_token: params[:id])
    new_password = password_reset_params[:password]

    # Automatically set `#password_confirmation` so user does not have
    # to enter in password twice on reset page.
    if @user&.update(password: new_password, password_confirmation: new_password)
      let_user_in(@user)
    else
      render :show
    end
  end

  private

  def password_reset_params
    params.require(:user).permit(:password)
  end
# app/models/user.rb
class User < ApplicationRecord
  PASSWORD_RESET_TIME_LIMIT_IN_HOURS = 4.freeze

  has_secure_password
  has_secure_token :password_reset_token

  validates :password,
    presence: true,
    length: { minimum: 8 },
    allow_nil: true

  def password_reset_token_expired?
    return true if password_reset_token_sent_at.blank?
    password_reset_token_sent_at < PASSWORD_RESET_TIME_LIMIT_IN_HOURS.hours.ago
  end
end

click_on 不保证点击触发的任何操作在 returns 时发生。这是因为 Capybara 无法知道该点击会触发什么(如果有的话)操作。这意味着您对新密码的断言可能发生在页面提交之前。要解决此问题,您需要使用 Capybara 提供的重试断言之一(assert_equal 不是)来检查页面上是否存在指示更新已发生的可见内容。

类似于

click_on "Update Password"
assert_text "Password Updated!" # whatever message your page shows to indicate successful password update

assert_equal(@user.reload.password, new_password)

应该可以解决您的问题。