PasswordResetsコントローラーの、ここまでに実装してきた内容に対するテスト
PasswordResetsコントローラーのnew
アクションおよびcreate
アクションに対するテストは、Railsチュートリアル本文の内容に合わせていくと、以下のような内容になります。
require 'test_helper'
class PasswordResetsTest < ActionDispatch::IntegrationTest
def setup
ActionMailer::Base.deliveries.clear
@user = users(:rhakurei)
end
test "password resets" do
get new_password_reset_path
assert_template 'password_resets/new'
# メールアドレスが無効
post password_resets_path, params: { password_reset: { email: "" } }
assert_not flash.empty?
assert_template 'password_resets/new'
get new_password_reset_path
assert flash.empty?
# メールアドレスが有効
post password_resets_path, params: { password_reset: { email: @user.email} }
assert_not_equal @user.reset_digest, @user.reload.reset_digest
assert_equal 1, ActionMailer::Base.deliveries.size
assert_not flash.empty?
assert_redirected_to root_url
end
end
テスト実装の前提となる、PasswordResetsコントローラーのedit
アクションの動作
- 有効なメールアドレスと有効なパスワード再設定用トークンの組み合わせで
edit
アクションが呼び出された場合、type属性がhidden
であるinput要素に当該メールアドレスを格納する- 後続の
update
アクションで使用するため
- 後続の
- 無効な
edit
アクションの呼び出しがあった場合、/ にリダイレクトする- 無効なメールアドレス
- 有効化されていないユーザー
- 無効なパスワード再設定用トークン
PasswordResetsコントローラーのedit
アクションに対するテスト
上記前提を踏まえた上で、PasswordResetsコントローラーのedit
アクションに対するテストを追加していきましょう。
require 'test_helper'
class PasswordResetsTest < ActionDispatch::IntegrationTest
def setup
ActionMailer::Base.deliveries.clear
@user = users(:rhakurei)
end
test "password resets" do
get new_password_reset_path
assert_template 'password_resets/new'
# メールアドレスが無効
post password_resets_path, params: { password_reset: { email: "" } }
assert_not flash.empty?
assert_template 'password_resets/new'
get new_password_reset_path
assert flash.empty?
# メールアドレスが有効
post password_resets_path, params: { password_reset: { email: @user.email} }
assert_not_equal @user.reset_digest, @user.reload.reset_digest
assert_equal 1, ActionMailer::Base.deliveries.size
assert_not flash.empty?
assert_redirected_to root_url
+ # パスワード再設定フォームのテスト
+ user = assigns(:user)
+ # メールアドレスが無効
+ get edit_password_reset_path(user.reset_token, email: "")
+ assert_redirected_to root_url
+ # 無効なユーザー
+ user.toggle!(:activated)
+ get edit_password_reset_path(user.reset_token, email: user.email)
+ assert_redirected_to root_url
+ user.toggle!(:activated)
+ # メールアドレスが有効で、トークンが無効
+ get edit_password_reset_path('wrong token', email: user.email)
+ assert_redirected_to root_url
+ # メールアドレスもトークンも有効
+ get edit_password_reset_path(user.reset_token, email: user.email)
+ assert_template 'password_resets/edit'
+ assert_select "input[name=email][type=hidden][value=?]", user.email
end
end
PasswordResetsコントローラーのedit
アクションに対するテストの実装が完了した時点でテストを実行するとどうなるか
PasswordResetsコントローラーのedit
アクションに対するテストの実装が完了した時点で、一度テストを実行してみます。
# rails test test/integration/password_resets_test.rb
Running via Spring preloader in process 671
Started with run options --seed 36691
FAIL["test_password_resets", PasswordResetsTest, 2.968936000001122]
test_password_resets#PasswordResetsTest (2.97s)
Expected response to be a <3XX: redirect>, but was a <200: OK>
test/integration/password_resets_test.rb:29:in `block in <class:PasswordResetsTest>'
1/1: [===================================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.97318s
1 tests, 9 assertions, 1 failures, 0 errors, 0 skips
以下のようなメッセージが出力されてテストが失敗しています。
Expected response to be a <3XX: redirect>, but was a <200: OK>
「リダイレクトされるべきところ、リダイレクトされていない」という趣旨のメッセージですね。
無効なedit
アクションの呼び出しがあった場合、/ にリダイレクトする
- 無効なメールアドレス
- 有効化されていないユーザー
- 無効なパスワード再設定用トークン
以上のようなパラメータでedit
アクションの呼び出しが行われた場合、/ にリダイレクトされなければならない、という仕様でしたね。このような実装を実現するためには、PasswordResetsコントローラーのbeforeフィルターを使用します。
class PasswordResetsController < ApplicationController
+ before_action :get_user, only: [:edit, :update]
+ before_action :valid_user, only: [:edit, :update]
...略
+
+ private
+
+ def get_user
+ @user = User.find_by(email: params[:email])
+ end
+
+ # 正しいユーザーかどうか確認する
+ def valid_user
+ unless (@user && @user.activated? &&
+ @user.authenticated?(:reset, params[id]))
+ redirect_to root_url
+ end
+ end
end
「実際の判定と、/ へのリダイレクト」という処理の実体は、valid_user
メソッドで行われています。get_user
メソッドは、valid_user
メソッドで処理を行う前提として、@user
変数の内容を定義しています。
なお、@user
という変数名がcreate
アクション内でも使われていますが、create
アクションの@user
変数の内容と今回の@user
変数の内容が混ざり合うことはありません。get_user
メソッド・valid_user
メソッドともに、edit
アクションとupdate
アクションにしか関係しないからです。
無効なedit
アクションの呼び出しに対する処理を実装した時点でテストを実行するとどうなるか
# rails test test/integration/password_resets_test.rb
Running via Spring preloader in process 684
Started with run options --seed 51259
ERROR["test_password_resets", PasswordResetsTest, 3.9712250999982643]
test_password_resets#PasswordResetsTest (3.97s)
NameError: NameError: undefined local variable or method `email' for #<PasswordResetsController:0x00005629bcfa2850>
app/controllers/password_resets_controller.rb:27:in `get_user'
test/integration/password_resets_test.rb:28:in `block in <class:PasswordResetsTest>'
1/1: [===================================] 100% Time: 00:00:03, Time: 00:00:03
Finished in 3.97519s
1 tests, 8 assertions, 0 failures, 1 errors, 0 skips
以下のようなエラーが出てテストが失敗しています。
NameError: undefined local variable or method `email'
「email
という未定義の変数またはメソッドが呼び出されている」というエラーです。「edit
アクションの呼び出しの際、クエリパラメータにemail
が含まれるようにする」必要がありますね。
edit
アクションの呼び出しの際、クエリパラメータにemail
が含まれるようにする
この段階で、PasswordResetsリソースのedit
アクションに対するビューを実装していきます。「クエリパラメータにemail
が含まれるようにすること」も含める必要がありますね。実装場所はapp/views/password_resets/edit.html.erb
です。
<% provide(:title, 'Reset password') %>
<h1>Reset password</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(@user, url: password_reset_path(params[:id])) do |f| %>
<%= render 'shared/error_messages'%>
<%= hidden_field_tag :email, @user.email %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit "Update password", class: "btn btn-primary" %>
<% end %>
</div>
</div>
hidden_field_tag
というメソッドの意味
f.hidden_field :email, @user.email
f.hidden_field
を使った以上のような呼び出しの場合、メールアドレスはparams[:user][:email]
に保存されます。
hidden_field_tag :email, @user.email
一方、hidden_field_tag
を使った以上のような呼び出しの場合、メールアドレスはparams[:email]
に保存されます。
今回は、params[:email]
を使った以下のような呼び出しが行われるのが前提です。「正当なクエリパラメータが構築されるためには、受け側にはf.hidden_field
ではなくhidden_field_tag
が必要になる」ということですね。
@user = User.find_by(email: params[:email])
PasswordResetsコントローラーのedit
アクションの実装が完了
ここまでの実装が完了した時点で、PasswordResetsコントローラーのedit
アクションに対するテストを実行してみます。
# rails test test/integration/password_resets_test.rb
Running via Spring preloader in process 749
Started with run options --seed 30899
1/1: [===================================] 100% Time: 00:00:05, Time: 00:00:05
Finished in 5.38865s
1 tests, 13 assertions, 0 failures, 0 errors, 0 skips
テストが成功しました。これで「PasswordResetsコントローラーのedit
アクションの実装は完了した」といえますね。
PasswordResetsコントローラーのedit
アクションに対するテストは、逆にどのような実装抜けがあったらテストが失敗するのか
別記事にて解説しています。