Railsチュートリアル 第12章 パスワードの再設定 - PasswordResets#createで、メールアドレスが無効な場合の処理をテスト駆動で実装していく

# 何について書いているか

「Railsチュートリアル 第12章 パスワードの再設定」の「[`create`アクションでパスワード再設定](https://railstutorial.jp/chapters/password_reset?version=5.1#sec-password_reset_create_action)」のうち、メールアドレスが無効な場合の実装を、テスト駆動で行っていこうという内容です[^なぜ]。

[^なぜ]: Railsチュートリアル本文においては、「先に実装を書いて、後から一気にテストを書いていく」という進め方をしています。しかしながら、個人的には、「テストを先に実装して、後から実装で追いかけていく」ほうが理解しやすいと思ったのです。

# メールアドレスが無効な場合の処理に対するテストの実装

Railsチュートリアル本文の通りに実装していくとすれば、以下のテストが「メールアドレスが無効な場合の処理に対するテスト」に該当することになります。

```diff: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'
    end
  end
```

## 現時点でテストが失敗することの確認

```
# rails test test/integration/password_resets_test.rb
Running via Spring preloader in process 77
Started with run options --seed 17171

ERROR["test_password_resets", PasswordResetsTest, 2.4794358999999986]
 test_password_resets#PasswordResetsTest (2.48s)
AbstractController::ActionNotFound:         AbstractController::ActionNotFound: The action 'create' could not be found for PasswordResetsController
            test/integration/password_resets_test.rb:14:in `block in <class:PasswordResetsTest>'

  1/1: [===================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.48535s
1 tests, 1 assertions, 0 failures, 1 errors, 0 skips
```

現時点でテストが失敗しているのは、以下の処理がエラーとなるためです。

```ruby:test/integration/password_resets_test.rb（14行目）
post password_resets_path, params: { password_reset: { email: "" } }
```

そもそも`app/controllers/password_resets_controller.rb`に`create`メソッドが存在しないためですね。

# `create`メソッドの定義

```diff:PasswordResetsController
  class PasswordResetsController < ApplicationController
    def new
    end
+
+   def create
+   end

    def edit
    end
  end
```

## `create`メソッドを定義したことにより、テストはどうなるか

```
# rails test test/integration/password_resets_test.rb
Running via Spring preloader in process 90
Started with run options --seed 62884

 FAIL["test_password_resets", PasswordResetsTest, 3.0227256000002853]
 test_password_resets#PasswordResetsTest (3.02s)
        Expected true to be nil or false
        test/integration/password_resets_test.rb:15:in `block in <class:PasswordResetsTest>'

  1/1: [===================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 3.02778s
1 tests, 2 assertions, 1 failures, 0 errors, 0 skips
```

```ruby:test/integration/password_resets_test.rb（15行目）
assert_not flash.empty?
```

「フラッシュメッセージが空であってはならないところ、フラッシュメッセージが空である」という理由でテストが失敗していますね。

# フラッシュメッセージの定義

ひとまずはこのような感じの実装になります。

```diff:PasswordResetsController#create
  def create
+   if false #TODO: 有効なユーザー情報を与えられるようにする
+     #TODO: 有効なメールアドレスが与えられた場合の処理を実装する
+   else
+     flash.now[:danger] = "Email address not found"
+   end
  end
```

## フラッシュメッセージを定義したことにより、テストはどうなるか

```
# rails test test/integration/password_resets_test.rb
Running via Spring preloader in process 103
Started with run options --seed 35812

 FAIL["test_password_resets", PasswordResetsTest, 3.379763599999933]
 test_password_resets#PasswordResetsTest (3.38s)
        expecting <"password_resets/new"> but rendering with <[]>
        test/integration/password_resets_test.rb:16:in `block in <class:PasswordResetsTest>'

  1/1: [===================================] 100% Time: 00:00:03, Time: 00:00:03

Finished in 3.38277s
1 tests, 3 assertions, 1 failures, 0 errors, 0 skips
```

```ruby:test/integration/password_resets_test.rb（16行目）
assert_template 'password_resets/new'
```

「`password_resets/new`がブラウザに返されるべきところ、何もブラウザに返されていない」という理由でテストが失敗していますね。

# `new`ビューをブラウザに返すようにする

このような感じの実装になります。

```diff:PasswordResetsController#create
  def create
    if false #TODO: 有効なユーザー情報を与えられるようにする
      #TODO: 有効なメールアドレスが与えられた場合の処理を実装する
    else
      flash.now[:danger] = "Email address not found"
+     render 'new'
    end
  end
```

## `new`ビューをブラウザに返すようにしたことにより、テストはどうなるか

```
# rails test test/integration/password_resets_test.rb
Running via Spring preloader in process 116
Started with run options --seed 5029

  1/1: [===================================] 100% Time: 00:00:01, Time: 00:00:01

Finished in 1.94821s
1 tests, 3 assertions, 0 failures, 0 errors, 0 skips
```

ここまでのテストは成功するようになりました。これでひとまず「メールアドレスが無効な場合の処理」は完成といえるでしょう。

# 実は、最初に実装したテストはまだ不十分だった

この記事を書いた後に気がついたのですが、実は、Railsチュートリアル本文に書かれたテストでは、一つ検出できないバグがあります。すなわち、以下のようなコードを書いてしまった場合に発生するバグです。

```ruby:PasswordResetsController#create（バグあり）
def create
  if false #TODO: 有効なユーザー情報を与えられるようにする
    #TODO: 有効なメールアドレスが与えられた場合の処理を実装する
  else
    flash[:danger] = "Email address not found"  # <-- flash.nowではなくflash
  end
end
```

正しくは以下のコードになります。

```diff:PasswordResetsController#create
  def create
    if false #TODO: 有効なユーザー情報を与えられるようにする
      #TODO: 有効なメールアドレスが与えられた場合の処理を実装する
    else
-     flash[:danger] = "Email address not found"
+     flash.now[:danger] = "Email address not found"
    end
  end
```

詳細については、以下の別項目で解説しています。

* [Railsチュートリアル 第12章 パスワードの再設定 - ソースコードに混入したバグと、テストに対する一つのリファクタリング](https://qiita.com/rapidliner00/items/dabe5dea7b681b8535f1)