35歳だけどRailsチュートリアルやってみた。[第4版 9章 9.3 [Remember me] のテスト まとめ&解答例]

#はじめに
最近、プロジェクト管理業務が業務の大半を占めており、
プログラムを書く機会がなかなかありません。

このままだとプログラムがまったく書けない人になってしまう危機感（迫り来る35歳定年説）と、
新しいことに挑戦したいという思いから、
[Ruby on Rails チュートリアル実例を使ってRailsを学ぼう 第4版](https://railstutorial.jp/)を学習中です。
業務で使うのはもっぱらJavaなのですが、Rails楽しいですね。


これまでEvernoteに記録していましたが、ソースコードの貼付けに限界を感じたため、
Qiitaで自分が学習した結果をアウトプットしていきます。

個人の解答例なので、誤りがあればご指摘ください。

##動作環境
- cloud9
- ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-linux]
- Rails 5.0.0.1


##9.3.1 [Remember me] ボックスをテストする
###本章での学び
####単体テストでログインできるようにする
テスト内でユーザがログインできるヘルパーメソッドを定義する。

```/sample_app/test/test_helper.rb
def log_in_as(user)
  session[:user_id] = user.id
end
```

####結合テストでログインできるようにする
結合テスト内でもテストユーザとしてログインできるヘルパーメソッドを定義する。

```/sample_app/test/test_helper.rb
class ActionDispatch::IntegrationTest
  # テストユーザとしてログインする
  def log_in_as(user, password: 'password', remember_me: '1')
    post login_path, params: { session: { email: user.email,
                                          password: password,
                                          remember_me: remember_me } }
  end
end
```

####テストケースの作成
ユーザのログインのテストケースに２つのケース追加する。
以下、注意点。

- テストケースの中で、`cookies[:user_id]`のように、cookiesメソッドにシンボルは使えない
- `cookies['remember_token']`のように、文字列シンボルなら使える

#####テストケース１
- テストユーザとしてログインする（ユーザ情報は記憶する）
- `remember_token`がnilではないことを確認する

```/sample_app/test/integration/users_login_test.rb
```

#####テストケース２
- テストユーザとしてログインする（ユーザ情報は記憶しない）
- `remember_token`がnilであることを確認する

```/sample_app/test/integration/users_login_test.rb
```


###演習1
*リスト 9.25の統合テストでは、仮想のremember_token属性にアクセスできないと説明しましたが、実は、assignsという特殊なテストメソッドを使うとアクセスできるようになります。コントローラで定義したインスタンス変数にテストの内部からアクセスするには、テスト内部でassignsメソッドを使用します。このメソッドにはインスタンス変数に対応するシンボルを渡します。 たとえば、createアクションで@userというインスタンス変数が定義されていれば、テスト内部ではassigns(:user)と書くことでインスタンス変数にアクセスできます。 本チュートリアルのアプリケーションの場合、Sessionsコントローラのcreateアクションでは、userを (インスタンス変数ではない) 通常のローカル変数として定義しましたが、これをインスタンス変数に変えてしまえば、cookiesにユーザーの記憶トークンが正しく含まれているかどうかをテストできるようになります。 このアイディアに従ってリスト 9.27とリスト 9.28の不足分を埋め (ヒントとして?やFILL_INを目印に置いてあります)、[remember me] チェックボックスのテストを改良してみてください。*

`create`アクション内で、ローカル変数`user`をインスタンス変数`@user`に書き換える。

```/sample_app/app/controllers/sessions_controller.rb
  def create
    @user = User.find_by(email: params[:session][:email].downcase)
    if @user && @user.authenticate(params[:session][:password])
      #ユーザログイン後にユーザ情報のページヘリダイレクトする
      log_in @user
      #チェックされていたらユーザを記憶する
      params[:session][:remember_me] == '1' ? remember(@user) : forget(@user)
      redirect_to @user
    else
```

統合テストケース内で、`assigns(:user)`を追加する。

```/sample_app/test/integration/users_login_test.rb
assert_equal cookies['remember_token'], assigns(:user).remember_token
```

テストを実行し、greenになることを確認。

```irb
yokoyan:~/workspace/sample_app (advanced-login) $ rails test
Running via Spring preloader in process 1914
Started with run options --seed 28794

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

Finished in 1.09513s
26 tests, 71 assertions, 0 failures, 0 errors, 0 skips
```


##9.3.2 [Remember me] をテストする
###本章での学び
####テストコードに例外処理を埋め込む
current_userヘルパーメソッド内をテストしていないため、わざと例外処理を入れる。
例外処理を入れたにも関わらず、テストをパスしたら、該当部分がテストされていないことがわかる。
テストを忘れている疑いがあるコードをテストする際に有効なテクニック。

例外処理を意図的に発生させるためには、[raise](https://docs.ruby-lang.org/ja/search/query:raise/version:2.3.0/)を使う。

>自身が表すスレッドで強制的に例外を発生させます。

```/sample_app/app/helpers/sessions_helper.rb
  # 現在のログイン中のユーザーを返す（いる場合）
  def current_user
    if (user_id = session[:user_id])
      @current_user ||= User.find_by(id: user_id)
    elsif (user_id = cookies.signed[:user_id])
      raise # テストがパスすれば、この部分がテストされていないことがわかる
      user = User.find_by(id: user_id)
      if user && user.authenticated?(cookies[:remember_token])
        # ログイン処理
        log_in(user)
        @current_user = user
      end
    end
  end
```

テストを実行すると、greenとなる。（例外が発生しない）

```irb
yokoyan:~/workspace/sample_app (advanced-login) $ rails test
Running via Spring preloader in process 1737
Started with run options --seed 39993

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

Finished in 1.34949s
26 tests, 71 assertions, 0 failures, 0 errors, 0 skips
```

####ヘルパーメソッドのテスト
以下の3点を確認するテストを作成する。

1.fixtureで`user`変数を定義する

```rb
  @user = users(:michael)
```

2.渡されたユーザを`remember`メソッドで定義する

```rb
  remember(@user)
```

3.`current_user`が渡されたユーザと同じであることを確認する。また、ログイン状態かどうかを確認する。

```rb
  # assert_equal 期待値, 実際の値
  assert_equal @user, current_user
  assert is_logged_in?
```


テストを実行すると、redになる。（raiseで例外が発生している）

```irb
yokoyan:~/workspace/sample_app (advanced-login) $ rails test test/helpers/sessions_helper_test.rb 
Running via Spring preloader in process 2674
Started with run options --seed 41311

ERROR["test_current_user_returns_nil_when_remember_digest_is_wrong", SessionsHelperTest, 0.05904175399336964]
 test_current_user_returns_nil_when_remember_digest_is_wrong#SessionsHelperTest (0.06s)
RuntimeError:         RuntimeError: 
            app/helpers/sessions_helper.rb:23:in `current_user'
            test/helpers/sessions_helper_test.rb:17:in `block in <class:SessionsHelperTest>'

ERROR["test_current_user_returns_right_user_when_session_is_nil", SessionsHelperTest, 0.06918600399512798]
 test_current_user_returns_right_user_when_session_is_nil#SessionsHelperTest (0.07s)
RuntimeError:         RuntimeError: 
            app/helpers/sessions_helper.rb:23:in `current_user'
            test/helpers/sessions_helper_test.rb:11:in `block in <class:SessionsHelperTest>'

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

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

raiseを外してテストを再実行すると、greenになることを確認。

```irb
yokoyan:~/workspace/sample_app (advanced-login) $ rails test test/helpers/sessions_helper_test.rb 
Running via Spring preloader in process 1608
Started with run options --seed 48069

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

Finished in 0.07859s
2 tests, 3 assertions, 0 failures, 0 errors, 0 skips
```


###演習1
*リスト 9.33にあるauthenticated?の式を削除すると、リスト 9.31の２つ目のテストで失敗することを確かめてみましょう (このテストが正しい対象をテストしていることを確認してみましょう)。*

該当箇所をコメントアウトする。

```/sample_app/app/helpers/sessions_helper.rb
  # 現在のログイン中のユーザーを返す（いる場合）
  def current_user
    if (user_id = session[:user_id])
      @current_user ||= User.find_by(id: user_id)
    elsif (user_id = cookies.signed[:user_id])
      user = User.find_by(id: user_id)
      if user
#      if user && user.authenticated?(cookies[:remember_token])
```

テストを実行すると、redになることを確認。

```irb
yokoyan:~/workspace/sample_app (advanced-login) $ rails test test/helpers/sessions_helper_test.rb 
Running via Spring preloader in process 1663
Started with run options --seed 43091

 FAIL["test_current_user_returns_nil_when_remember_digest_is_wrong", SessionsHelperTest, 0.06601983698783442]
 test_current_user_returns_nil_when_remember_digest_is_wrong#SessionsHelperTest (0.07s)
        Expected #<User id: 762146111, name: "Michael Example", email: "michael@example.com", created_at: "2017-05-29 21:37:58", updated_at: "2017-05-29 21:37:58", password_digest: "$2a$04$JRr0GEmdxOPGIOMV0.MXr.RZc8lyrDK8lgPqBfbNHKB...", remember_digest: "$2a$04$4OGtkkjBH4SzRn8t.yaEZ.vMue2zpuqL9zx3FlFhCip..."> to be nil.
        test/helpers/sessions_helper_test.rb:17:in `block in <class:SessionsHelperTest>'

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

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

##メンテナンスモードを使ったherokuへのデプロイ
以下のコマンドでherokuで動く本番環境をメンテナンスモードにできる。

```irb
$ heroku maintenance:on
```
![image](https://qiita-image-store.s3.amazonaws.com/0/98130/c8be78eb-3c1e-0da9-0bee-c02b30b15359.png)

メンテナンスモードに切り替わったら、herokuへデプロイする。

```irb
$ git push heroku
$ heroku run rails db:migrate
```

デプロイが完了したら、メンテナンスモードをオフにする。

```irb
$ heroku maintenance:off
```

#おわりに
ロジックが複雑になってきた分、テストケースも難しくなってきました。
単体テストだけでなく、結合テストもコード化しているのは新鮮です。
（ぜひうちの会社でもやりたい・・・）