Railsチュートリアルの第４版をRSpecでテスト-2

[Ruby on Rails チュートリアル][link-1]の第4版を学習し、[Everyday Rails - RSpecによるRailsテスト入門][link-2]でRSpecを学習したので、[Ruby on Rails チュートリアル][link-1]をRSpecでテストしてみました。
今回は第7章～第8章のテストを書きます。

第3章～第6章のテストは以下。
[Railsチュートリアルの第4版をRSpecでテスト-1][link-3]
[link-1]:https://railstutorial.jp/
[link-2]:https://leanpub.com/everydayrailsrspec-jp
[link-3]:https://qiita.com/yano12/items/eba8693272c31d6af7f0
[link-4]:https://qiita.com/yano12/items/4ef93129a50249adcb8d
[link-5]:https://qiita.com/yano12/items/319615e6e5fdcd7d844a
[link-6]:https://qiita.com/yano12/items/99e3fd4213f61339bad4
[link-7]:https://railstutorial.jp/chapters/account_activation?version=5.1#sec-exercises_mailer_templates
[link-8]:https://qiita.com/yano12/items/f5167416f3c20e1896d4
[link-9]:https://railstutorial.jp/chapters/password_reset?version=5.1#cha-password_reset
[link-10]:https://qiita.com/IrieLuka/items/34b0edccecc036d24748
# ユーザーコントローラーのテスト
まず初めにユーザーコントローラーをテストしていきますが、[Everyday Rails - RSpecによるRailsテスト入門][link-2]によると

>さあこれでみなさんはコントローラスペックとリクエストスペックの両方を書けるようになりました。では、どちらのテストを書くべきでしょうか？第5章でもお話ししたとおり、私はコントローラスペックよりも統合スペック（フィーチャスペックとリクエストスペック）を強くお勧めします。なぜならRails におけるコントローラスペックは重要性が低下し、かわりにより高いレベルのテストの重要性が上がってきているためです。こうしたテストの方がアプリケーションのより広い範囲をテストすることができます。

らしいので、今回はリクエストスペックでユーザーコントローラーのテストを書いていきます。

# newアクションのテスト
初めにnewアクションをテストします。
spec/requests/user_spec.rbを作成し、新規登録ページが正しく表示されることをテストします。

```spec/requests/users_spec.rb
require 'rails_helper'

RSpec.describe "User pages", type: :request do
  let(:user) { FactoryBot.create(:user) }
  
  # new
  describe "GET #new" do
    # 正常なレスポンスを返すこと
    it "returns http success" do
      get signup_path
      expect(response).to be_success
      expect(response).to have_http_status "200"
    end
  end
end
```

実行しテストがパスするのを確認します。

## showアクションのテスト
続いてユーザー表示ページのテストですが、ログインしている場合のみ表示されることをテストするため、ログイン済みユーザーとゲストユーザー2通りのテストを書きます。

```spec/requests/user_spec.rb
require 'rails_helper'

RSpec.describe "User pages", type: :request do
  let(:user) { FactoryBot.create(:user) }
  
  # newアクションのテストは省略
  
  describe "GET #show" do
    
    # ログイン済みのユーザーとして
    context "as an authenticated user" do
      # 正常なレスポンスを返すこと
      it "responds successfully" do
        sign_in_as user
        get user_path(user)
        expect(response).to be_success
        expect(response).to have_http_status "200"
      end
    end
    
    # ログインしていないユーザーの場合
    context "as a guest" do 
      # ログイン画面にリダイレクトすること
      it "redirects to the login page" do
        get user_path(user)
        expect(response).to redirect_to login_path
      end
    end
    
    # アカウントが違うユーザーの場合
    context "as other user" do 
      before do
        @other_user = FactoryBot.create(:user)
      end
      
      # ホーム画面にリダイレクトすること
      it "redirects to the login page" do
        sign_in_as @other_user
        get user_path(user)
        expect(response).to redirect_to root_path
      end
    end
  end
end
```

そしてsign_in_asメソッドをspec/support/utilities.rbで定義します。

```spec/support/utilities.rb
# 他のメソッドは省略

# ログインする
def sign_in_as(user)
  post login_path, params: { session: { email: user.email,
                                      password: user.password } }
end
```

ここでテストを実行するとテストが失敗するので、ユーザーファクトリを修正しテストユーザーを有効化します。

```spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name "Example"
    sequence(:email) { |n| "tester#{n}@example.com" }
    password "password"
    password_confirmation "password"
    # 有効化
    activated true
  end
end
```

テストを実行しパスするのを確認します。

# ユーザー登録のテスト(※windowsでやると何故か失敗した)
続いてユーザー登録をテストしますが、私の場合、初めにwindowsのPCを使ってテストするとアカウント有効化の部分がパスせず、後日macでやると何故かパスしました。
開発環境はAWSのCloud9を使用しているのでOSは関係ないと思うのですが、理由がわかる方いましたら教えていただきたいです。


ユーザー登録のテストはフィーチャスペックに書いていきます。
フィーチャスペックでは、ブラウザの操作(リンクをクリックしたり、Webフォームを入力したり)をシミュレートするためにCapybaraを使います。
CpaybaraはRails5.1以後ではもとからインストールされていますが、インストールされていない場合は以下をGemfileに追加します。

```Gemfile.
group :test do
  # 他のgemは省略
  gem 'capybara', '>= 2.15'
end
```

また、rails_helper.rbでテストインストールに対してCapybaraを読み込むように伝えます。

```spec/rails_helper.rb
# This file is copied to spec/ when you run 'rails generate rspec:install'
require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
# Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'spec_helper'
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!
require 'capybara/rspec'

# RSpecの設定が続く...
```

これでCapybaraを使う準備は完了です。
以下を実行してユーザー登録のテストを書くためのフィーチャスペックを作成します。

```
$ bin/rails g rspec:feature sign_up
```

次に、ユーザーがサインアップするためのステップと期待される結果をこのファイルに追加します。

```spec/features/sign_ups_spec.rb
require 'rails_helper'

RSpec.feature "SignUps", type: :feature do
  include ActiveJob::TestHelper
  
  # ユーザーはサインアップに成功する
  scenario "user successfully signs up" do
    visit root_path
    click_link "新規会員登録"
    
    click_link "メールアドレスで登録"
    
    perform_enqueued_jobs do
      expect {
        fill_in "名前",              with: "Example"
        fill_in "メールアドレス",     with: "test@example.com"
        fill_in "パスワード",         with: "test123"
        fill_in "パスワード（確認）",  with: "test123"
        click_button "Sign up"
      }.to change(User, :count).by(1)
      
      expect(page).to have_content "アカウント有効化メールを送信しました。"
      expect(current_path).to eq root_path
    end
    
    # 以下はアカウント有効化メールのテストです
    # 詳細なテストは後で追加します
    mail = ActionMailer::Base.deliveries.last
    
    aggregate_failures do
      expect(mail.to).to eq ["test@example.com"]
      expect(mail.from).to eq ["noreply@example.com"]
      expect(mail.subject).to eq "Account activation"
    end
  end
end
```

また、アカウント有効化メールをテストするためにアプリケーションのdevelopment環境とtest環境の設定を追加します。

```config/environments/development.rb
Rails.application.configure do
  .
  .
  .
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :test
  host = 'example.com' # ホスト名
  config.action_mailer.default_url_options = { host: host, protocol: 'https' }
  .
  .
  .
end
```

```config/environments/test.rb
Rails.application.configure do
  .
  .
  .
  config.action_mailer.delivery_method = :test
  config.action_mailer.default_url_options = { host: 'example.com' } #自分のホスト名
  .
  .
  .
end
```

テストを実行すると初めは以下のエラーが出ていましたが、数日後にmacでテストするとパスしました。

```
Failures:

  1) SignUps user successfully signs up
     Failure/Error: <%= link_to "Activate", edit_account_activation_url(@user.activation_token, email: @user.email) %>
     
     ActionView::Template::Error:
       Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true
```



# サインインのテスト
ユーザーのログインテストはログイン成功時と失敗時の2つのテストを書いていきます。
spec/features/login_spec.rbを作成し以下を追加します。

```spec/features/login_spec.rb
require 'rails_helper'

RSpec.feature "Login", type: :feature do
  let(:user) { FactoryBot.create(:user) }
  
  # ログインに成功すること
  scenario "user successfully login" do
    valid_login(user)
    
    expect(current_path).to eq user_path(user)
    expect(page).to_not have_content "ログインする"
  end
  
  # 無効な情報ではログインに失敗すること
  scenario "user doesn't login with invalid information" do
    visit root_path
    click_link "ログイン"
    fill_in "メールアドレス", with: ""
    fill_in "パスワード", with: ""
    click_button "ログインする"
    
    expect(current_path).to eq login_path
    expect(page).to have_content "ログインする"
    expect(page).to have_content "メールアドレスまたはパスワードが正しくありません。"
  end
end
```

また、spec/support/login_support.rbを作成し、valid_loginメソッドを追加します。
サポートモジュールを使うことでコードの重複を失くし、ログイン処理が変わった場合でも全てのテストコードを変更する必要がなくなるので便利です。

```spec/support/login_support.rb
# 新しいモジュールを作成
module LoginSupport
  def valid_login(user)
    visit root_path
    click_link "ログイン"
    fill_in "メールアドレス", with: user.email
    fill_in "パスワード", with: user.password
    click_button "ログインする"
  end
end

# LoginSupportモジュールをincludeする
RSpec.configure do |config|
  config.include LoginSupport
end
```

モジュールの定義の後にはRSpecの設定が続きます。
ここでは、RSpec.configureを使って新しく作ったモジュール（LoginSupport）をinculdeしています。

テストを実行しパスするのを確認します。

# サインアウトのテスト
サインインに続いてサインアウトのテストを書いていきます。
先ほどのspec/features/login_spec.rbにログアウトのテストを追加します。

```spec/features/login_spec.rb
require 'rails_helper'

RSpec.feature "Login", type: :feature do
  let(:user) { FactoryBot.create(:user) }
  
  # ログインに成功しログアウトすること
  scenario "user successfully login" do
    valid_login(user)
    
    expect(current_path).to eq user_path(user)
    expect(page).to_not have_content "ログイン"
    
    # ログアウトのテスト
    click_link "ログアウト"
    
    expect(current_path).to eq root_path
    expect(page).to have_content "ログイン"
  end
# ・・・以下省略
```

テストを実行しパスするのを確認します。

今回は以上です。
第9章以降は完成し次第投稿する予定です。

#追記
第9章以降のテストを書きました。

 - [Railsチュートリアルの第4版をRSpecでテスト-3][link-5]（第9章）
 - [Railsチュートリアルの第4版をRSpecでテスト-4][link-6]（第10章）
 - [Railsチュートリアルの第4版をRSpecでテスト-5][link-8]（第11章）
 - [Railsチュートリアルの第4版をRSpecでテスト-6][link-10]（第12章）