【RSpec】Railsチュートリアル第6版　第8章

#はじめに
Railsチュートリアル第6版のテストをRSpecで書き直していく。

###目次
- [第3章](https://qiita.com/supyolo888/items/35081ead2cfafb40a995)
- [第4章](https://qiita.com/supyolo888/items/939b670b62eb18467f6d)
- [第5章](https://qiita.com/supyolo888/items/42234ac2ed3919b14586)
- [第6章](https://qiita.com/supyolo888/items/bd10e64c77fe391cf203)
- [第7章](https://qiita.com/supyolo888/items/d2824ced26402b5f58d1)
- 第8章（本記事）
- [第9章](https://qiita.com/supyolo888/items/62e471b07a0c9a81575d)
- [第10章](https://qiita.com/supyolo888/items/a8695b20ec8082f85223)
- [第11章](https://qiita.com/supyolo888/items/5c81331e46f1d9d5ab3c)
- [第12章](https://qiita.com/supyolo888/items/fbb95671d39dfbf9df90)
- [第13章](https://qiita.com/supyolo888/items/c88b6acd4ff55750defc)
- [第14章](https://qiita.com/supyolo888/items/532390902bf3e0f5348e)

#事前準備
Gemfileにテストデータ作成用のFactory Botを追加

```rb:Gemfile
group :development, :test do
  gem "factory_bot_rails"
end
```

#Minitest

###Sessionsコントローラのテスト

```rb:test/controllers/sessions_controller_test.rb
require 'test_helper'

class SessionsControllerTest < ActionDispatch::IntegrationTest

  test "should get new" do
    get login_path
    assert_response :success
  end
end
```

###ユーザーログイン処理に対するテスト

```rb:test/integration/users_login_test.rb
require 'test_helper'

class UsersLoginTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "login with valid email/invalid password" do
    get login_path
    assert_template 'sessions/new'
    post login_path, params: { session: { email:    @user.email,
                                          password: "invalid" } }
    assert_not is_logged_in?
    assert_template 'sessions/new'
    assert_not flash.empty?
    get root_path
    assert flash.empty?
  end

  test "login with valid information followed by logout" do
    get login_path
    post login_path, params: { session: { email:    @user.email,
                                          password: 'password' } }
    assert is_logged_in?
    assert_redirected_to @user
    follow_redirect!
    assert_template 'users/show'
    assert_select "a[href=?]", login_path, count: 0
    assert_select "a[href=?]", logout_path
    assert_select "a[href=?]", user_path(@user)
    delete logout_path
    assert_not is_logged_in?
    assert_redirected_to root_url
    follow_redirect!
    assert_select "a[href=?]", login_path
    assert_select "a[href=?]", logout_path,      count: 0
    assert_select "a[href=?]", user_path(@user), count: 0
  end
end
```

###ユーザーログインのテストで使うfixture

```test/fixtures/users.yml
michael:
  name: Michael Example
  email: michael@example.com
  password_digest: <%= User.digest('password') %>
```

###テスト中のログインステータスを論理値で返すメソッド

```rb:test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
.
.
.
class ActiveSupport::TestCase
  fixtures :all

  def is_logged_in?
    !session[:user_id].nil?
  end
end
```

###ユーザー登録後のログインのテスト

```rb:test/integration/users_signup_test.rb
require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest
  .
  .
  .
  test "valid signup information" do
    get signup_path
    assert_difference 'User.count', 1 do
      post users_path, params: { user: { name:  "Example User",
                                         email: "user@example.com",
                                         password:              "password",
                                         password_confirmation: "password" } }
    end
    follow_redirect!
    assert_template 'users/show'
    assert is_logged_in?
  end
end
```


#RSpec

###Sessionsコントローラのテスト

```rb:spec/requests/sessions_spec.rb
require 'rails_helper'

RSpec.describe "Sessions", type: :request do

  it "returns http success" do
    get login_path
    expect(response).to have_http_status(:success)
  end

end

```

###ユーザーログイン処理に対するテスト

```rb:spec/system/users_login_spec.rb
require 'rails_helper'

RSpec.describe "UsersLogins", type: :system do
  
  let(:user) {FactoryBot.create(:user)}
  
  it "login with valid email/invalid password" do
    visit login_path
    expect(page).to have_current_path login_path
    fill_in "Email", with: user.email
    fill_in "Password", with: "invalid"
    click_button "Log in"
    expect(page).to have_current_path login_path
    expect(page).to have_content("Invalid email/password combination")
    visit root_path
    expect(page).to_not have_content("Invalid email/password combination")
  end

  it "login with valid information followed by logout" do
    visit login_path
    expect(page).to have_current_path login_path
    fill_in "Email", with: user.email
    fill_in "Password", with: user.password
    click_button "Log in"
    expect(page).to have_current_path user_path(user)
    expect(page).to_not have_link href: login_path
    expect(page).to have_link href: logout_path
    expect(page).to have_link href: user_path(user)
    visit root_path
    click_on "Log out"
    expect(page).to have_current_path root_path
    expect(page).to have_link href: login_path
    expect(page).to_not have_link href: logout_path
    expect(page).to_not have_link href: user_path(user)
  end

end
```

system specにおいて、sesssion機能は使用できないらしいので、*is_logged_in?*メソッドはrequest specにて別途記述する。

###ユーザーログインのテストで使うfactory

```rb:spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name {"Michael Example"}
    sequence(:email) { |n| "test#{n}@example.com" } 
    password {"password"}
    password_confirmation  {"password"}
  end
end
```

emailにシーケンスを使用し、ユニークで連続したメールアドレスを作成。

###テスト中のログインステータスを論理値で返すメソッド

```rb:spec/support/support.rb
module Support
  def is_logged_in?
    !session[:user_id].nil?
  end
end

RSpec.configure do |config|
  config.include Support
end
```
*spec/support*ディレクトリにサポートモジュールを作成し、`is_logged_in?`を追加。`RSpec.configure`でサポートモジュールを*include*する。

###is_logged_in?（ログイン時）

```rb:spec/requests/users_session_spec.rb
require 'rails_helper'

RSpec.describe "UsersSessions", type: :request do

  let(:user) {FactoryBot.create(:user)}

  it "log in and log out" do
    post login_path, params: { session: {
      email: user.email,
      password: user.password,
    } }
    expect(response).to redirect_to user_path(user)
    expect(is_logged_in?).to be_truthy 
    delete logout_path
    expect(response).to redirect_to root_path
    expect(is_logged_in?).to be_falsey
  end

end
```

###ユーザー登録後のログインのテスト

```rb:spec/requests/users_signups_spec.rb
require 'rails_helper'

RSpec.describe "UsersSignups", type: :request do

  let(:user) { FactoryBot.attributes_for(:user) }

  it "valid signup information" do
    expect {
      post users_path, params: { user: user }
    }.to change(User, :count).by(1)
    expect(response).to redirect_to user_path(User.last)
    expect(is_logged_in?).to be_truthy
  end

end
```

**ユーザーログイン処理に対するテスト**と同じく、system specにおいて、sesssion機能は使用できないので、request specを別途作成。

#発生するエラーについて（解決済み）

**ユーザーログイン処理に対するテスト**の中の`expect(page).to have_current_path "/"`のテストにおいて、
*Failure/Error: expect(page).to have_current_path "/"　expected "/logout" to equal "/"*というエラーが発生する。

また、**is_logged_in?**の中の`expect(response).to redirect_to user_path(@user)`のテストにおいて、
*Failure/Error: expect(response).to redirect_to user_path(@user)
       Expected response to be a <3XX: redirect>, but was a <422: Unprocessable Entity>*
というエラーが発生する。

#解決策

*spec/rails_helper.rb*内の

```rb:spec/rails.helper.rb
ENV['RAILS_ENV'] ||= 'test'
```

を

```rb:spec/rails.helper.rb
ENV['RAILS_ENV'] = 'test'
```

に変更する。

RSpecを作成した際、`ENV['RAILS_ENV'] ||= 'test'`がデフォルトで作成され、通常はRSpecの環境変数にはtestが適用されるはずが、なぜかRails.env="development"になっており、エラーが発生していた。
これにより、RSpecのデータベースに開発環境用のデータベースが使用されており、また通常テスト環境には適用されないCSRFチェックが働き、422エラーなどを発生させていた。
かなりハマってしまったので是非参考にしていただきたい。