Railsチュートリアル 第10章 フレンドリーフォワーディングの追加演習 - フレンドリーフォワーディングのリダイレクト先のURLが正しく渡されていることのテスト（editアクション編）

# 何についての記事か

「Railsチュートリアル 第10章 演習 - フレンドリーフォワーディング」の発展学習となります。

同演習には、「渡されたURLに初回のみ転送されていることを確認する。具体的には、リダイレクトのURLはデフォルト (プロフィール画面) に戻っていることを確認する。」という内容の出題があります。それであれば、逆に「『ログインしていない状態でユーザー情報編集ページにアクセスしようとした』という状況で、フレンドリーフォワーディングのリダイレクト先のURLが正しく渡されていること」のテストも必要なはずです。

というわけで、発展学習としてQiita記事を書いてみました。

# 内容

## フレンドリーフォワーディングのリダイレクト先のURLを保存する処理はどこにあるか

現在の実装では、「Usersコントローラーの`edit`アクションが呼び出される前には、同コントローラーの`logged_in_user`」メソッドが呼び出されるようになっています。そのコードは以下のとおりです。

```ruby:UsersController#logged_in_user
def logged_in_user
  unless logged_in?
    store_location
    flash[:danger] = "Please log in."
    redirect_to login_url
  end
end
```

「ログインしていない状態でUsersコントローラーの`edit`アクションが呼び出された」という状況においては、以下のコードが実行されます。

```ruby:
store_location
flash[:danger] = "Please log in."
redirect_to login_url
```

このうち、フレンドリーフォワーディングのリダイレクト先のURLを一時cookiesに保存するのは、`store_location`メソッドですね。当該メソッドは、Sessionsヘルパーに実装されています。

### `store_location`メソッドの実装

```ruby:SessionsHelper#store_location
def store_location
  session[:forwarding_url] = request.original_url if request.get?
end
```

「HTTPリクエストが`GET`である場合のみ、一時cookiesにフレンドリーフォワーディングのリダイレクト先のURLが保存される」という実装です。

## フレンドリーフォワーディングのリダイレクト先のURLを保存する処理に対応するテスト

上述コードに対応するテストコードは、`test/controllers/users_controller_test.rb`内にあります。`edit`アクションに対応するテストは"should redirect edit when not logged in"です。

```ruby:test/controllers/users_controller_test.rb（抜粋）
test "should redirect edit when not logged in" do
  get edit_user_path(@user)
  assert_not flash.empty?
  assert_redirected_to login_url
end
```

当該テストに記述を追加すれば、「『ログインしていない状態でユーザー情報編集ページにアクセスしようとした』という状況で、フレンドリーフォワーディングのリダイレクト先のURLが正しく渡されていること」がテストできるはずです。

実際に追加する記述は以下のようになります。

```diff:test/controllers/users_controller_test.rb（抜粋）
  test "should redirect edit when not logged in" do
    get edit_user_path(@user)
    assert_not flash.empty?
    assert_redirected_to login_url
+   assert_not_nil session[:forwarding_url]
  end
```

## 前提 - テストコードの特定行に対してテストを実行する

例えば、`test/controllers/users_controller_test.rb`の15行目に対応するテストを実行するには、`rails test`コマンドを以下のように呼び出せばOKです。

```
# rails test test/controllers/users_controller_test.rb:15
```

## `store_location`メソッドが呼び出されない場合に対するテスト

### 「`store_location`メソッドが呼び出されない場合」を再現する

`UsersController#logged_in_user`メソッドで、`store_location`メソッドが実行されないようにすればOKです。すぐにもとに戻すので、今回は「ソースコードからコメントアウト」という方法をとります。

```diff:app/controllers/users_controller.rb
  class UsersController < ApplicationController
    before_action :logged_in_user, only: [:edit, :update]
    before_action :correct_user,   only: [:edit, :update]

    ...略

    private

      ...略

      # beforeアクション

      # ログイン済みユーザーかどうか確認
      def logged_in_user
        unless logged_in?
-         store_location
+         # store_location
          flash[:danger] = "Please log in."
          redirect_to login_url
        end
      end

      ...略
  end
```

### 「`store_location`メソッドが呼び出されない場合」に、`edit`アクションに対するテストが失敗することを確認する

`edit`アクションに対するテストには、「should redirect edit when not logged in」という名前がついています。私の環境では、テスト「should redirect edit when not logged in」の始まりは、`test/controllers/users_controller_test.rb`の15行目となっています。

```ruby:test/controllers/users_controller_test.rb（15行目）
test "should redirect edit when not logged in" do
```

この行に対応するテストを実施してみます。

```
# rails test test/controllers/users_controller_test.rb:15
Running via Spring preloader in process 1220
Started with run options --seed 40280

 FAIL["test_should_redirect_edit_when_not_logged_in", UsersControllerTest, 0.5344693000079133]
 test_should_redirect_edit_when_not_logged_in#UsersControllerTest (0.54s)
        Expected nil to not be nil.
        test/controllers/users_controller_test.rb:19:in `block in <class:UsersControllerTest>'

  5/5: [===================================] 100% Time: 00:00:00, Time: 00:00:00

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

`test/controllers/users_controller_test.rb`の19行目でテストが失敗しましたね。当該行には、以下のコードが記述されています。

```ruby:test/controllers/users_controller_test.rb（19行目）
assert_not_nil session[:forwarding_url]
```

たった今追加したコードですね。想定したテストが正しく行えていることがわかりました。

### `UsersController#logged_in_user`メソッドを元に戻す

テストが正しく実装できていることがわかったので、`UsersController#logged_in_user`メソッドは元に戻しておきましょう。

```diff:app/controllers/users_controller.rb
  class UsersController < ApplicationController
    before_action :logged_in_user, only: [:edit, :update]
    before_action :correct_user,   only: [:edit, :update]

    ...略

    private

      ...略

      # beforeアクション

      # ログイン済みユーザーかどうか確認
      def logged_in_user
        unless logged_in?
-         # store_location
+         store_location
          flash[:danger] = "Please log in."
          redirect_to login_url
        end
      end

      ...略
  end
```

## `GET`リクエストに対して、一時cookiesにリダイレクト先のURLが保存されない場合に対するテスト

### 「`GET`リクエストに対して、一時cookiesにリダイレクト先のURLが保存されない場合」を再現する

`SessionsHelper#store_location`メソッドの記述を変更します。

```diff:app/helpers/sessions_helper.rb
  module SessionsHelper
    ...略

    # アクセスしようとしたURLを覚えておく
    def store_location
-     session[:forwarding_url] = request.original_url if request.get?
+     session[:forwarding_url] = request.original_url if !request.get?
    end
  end
```

`request.get?`を`!request.get?`に書き換えることにより、「`GET`リクエスト**以外の**リクエストに対して、一時cookiesにリダイレクト先のURLを保存する」という動作にしています。

### 「`GET`リクエストに対して、一時cookiesにリダイレクト先のURLが保存されない場合」に、`edit`アクションに対するテストが失敗することを確認する

```
# rails test test/controllers/users_controller_test.rb:15
Running via Spring preloader in process 1272
Started with run options --seed 50959

 FAIL["test_should_redirect_edit_when_not_logged_in", UsersControllerTest, 0.5778716999921016]
 test_should_redirect_edit_when_not_logged_in#UsersControllerTest (0.58s)
        Expected nil to not be nil.
        test/controllers/users_controller_test.rb:19:in `block in <class:UsersControllerTest>'

  5/5: [===================================] 100% Time: 00:00:00, Time: 00:00:00

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

先ほどと同じく、`test/controllers/users_controller_test.rb`の19行目でテストが失敗しました。想定したテストが正しく行えているといえます。

### `SessionsHelper#store_location`メソッドを元に戻す

テストが正しく実装できていることがわかったので、`SessionsHelper#store_location`メソッドは元に戻しておきましょう。

```diff:app/helpers/sessions_helper.rb
  module SessionsHelper
    ...略

    # アクセスしようとしたURLを覚えておく
    def store_location
-     session[:forwarding_url] = request.original_url if !request.get?
+     session[:forwarding_url] = request.original_url if request.get?
    end
  end
```

## 実装が正しいことの確認

改めて、`test/controllers/users_controller_test.rb`の15行目に対応するテストを実行します。

```
# rails test test/controllers/users_controller_test.rb:15
Running via Spring preloader in process 1298
Started with run options --seed 23019

  5/5: [===================================] 100% Time: 00:00:00, Time: 00:00:00

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

テストは無事成功しました。