Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

Ruby on Rails チュートリアルの第4版を学習し、Everyday Rails - RSpecによるRailsテスト入門でRSpecを学習したので、Ruby on Rails チュートリアルをRSpecでテストしてみました。
今回は第7章~第8章のテストを書きます。
第3章~第6章のテストは以下にあります。
Railsチュートリアルの第4版をRSpecでテスト-1

ユーザーコントローラーのテスト

まず初めにユーザーコントローラーをテストしていきますが、Everyday Rails - RSpecによるRailsテスト入門によると

さあこれでみなさんはコントローラスペックとリクエストスペックの両方を書けるようになりました。では、どちらのテストを書くべきでしょうか?第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章以降は完成し次第投稿する予定です。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした