0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Railsチュートリアル 第12章 パスワードの再設定 - PasswordResets#editの動作を、テスト駆動で実装していく

Posted at

PasswordResetsコントローラーの、ここまでに実装してきた内容に対するテスト

PasswordResetsコントローラーのnewアクションおよびcreateアクションに対するテストは、Railsチュートリアル本文の内容に合わせていくと、以下のような内容になります。

test/integration/password_resets_test.rb
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アクションに対するテストを追加していきましょう。

test/integration/password_resets_test.rb
  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フィルターを使用します。

app/controllers/password_resets_controller.rb
  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です。

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アクションに対するテストは、逆にどのような実装抜けがあったらテストが失敗するのか

別記事にて解説しています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?