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)
应该可以解决您的问题。
我正在使用 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)
应该可以解决您的问题。