88
88

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【動画付き】Railsチュートリアルの統合テスト(integration test)は、RSpecのリクエストスペックに置き換えるのがラクです

Last updated at Posted at 2019-07-09

はじめに

さきほど、こちらの記事を拝見しました。

RSpecのfeatureテストでsessionを扱う方法 - Qiita

詳しい内容はリンク先をチェックしていただきたいのですが、ざっくりまとめると、

  • RailsチュートリアルのテストをRSpecのフィーチャスペックに置き換えようとした
  • Railsチュートリアルのテストではsession変数を操作するヘルパーメソッドを使っていたが、フィーチャスペックではその方法がわからなかった
  • rack_session_accessというgemを使ったら、フィーチャスペックからsession変数を操作できた

というお話です。

ただ、僕はこの記事を読んで「うーん、僕はフィーチャスペックの中でわざわざsession変数を操作することはしないなあ」と思いました。

というわけで、この記事では「じゃあどうするのがベストだったのか?」という点を議論してみたいと思います。

TL; DR(最初に結論)

「どうするのがベストだったのか?」という問いに対する僕の回答は以下のようになります。

  • Rails標準の統合テスト(integration test)は、RSpecにおけるリクエストスペックに対応するものだと考えるのが良い。なので、フィーチャスペックよりもリクエストスペックに置き換える方がラクである。
  • どうしてもフィーチャスペック(もしくはシステムスペック)に置き換えたい場合は、テストの視点を「ブラウザを操作するユーザーの視点」に切り替える必要がある。
  • つまり、「統合テストでやっているテストはブラウザ上でどう操作するか?または、どう見えるか?」を頭の中で翻訳しなければいけないので、(不可能ではないものの)初心者には少しハードルが高い。

Railsチュートリアルに載っているテストコード

今回、Railsチュートリアルのテストコードはこちらのページで公開されているものを使います。

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
    # redirect_to @user
    follow_redirect!
    # Test
    assert_template 'users/show'
    assert is_logged_in?
  end
end

リクエストスペックで置き換えてみる

統合テストはリクエストスペックで置き換えるのがラクです。
リクエストスペックで書けば、統合テストとほとんど違いがありません。

spec/requests/users_signup_spec.rb
require "rails_helper"

RSpec.describe "Users signup", type: :request do
  example "valid signup information" do
    get signup_path
    expect {
      post users_path, params: { user: { name:  "Example User",
                                         email: "user@example.com",
                                         password:              "password",
                                         password_confirmation: "password" } }
    }.to change(User, :count).by(1)
    redirect_to @user
    follow_redirect!
    # Test
    assert_template 'users/show'
    assert is_logged_in?
  end
end

is_logged_in?メソッドは次のように定義します。
リクエストスペックであればsession変数もそのまま使えます。

spec/support/integration_helpers.rb
module IntegrationHelpers
  def is_logged_in?
    !session[:user_id].nil?
  end
end
spec/rails_helper.rb
# (省略)
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
# (省略)
RSpec.configure do |config|
  # (省略)
  config.include IntegrationHelpers, type: :request
end

補足1

ここではあまり深く考えずにIntegrationHelpersという名前を使いましたが、リクエストスペック用のヘルパーメソッドであれば、RequestHelpersみたいな名前の方が良かったかもしれません😓

補足2

assert_templateassertがRSpecでも動いてしまったので気づきませんでしたが、RSpecらしく書くのであれば次のように書いた方が良いと思います。

expect(response).to render_template('users/show')
expect(is_logged_in?).to be_truthy

システムスペックで置き換える場合

冒頭に紹介した記事では統合テストからフィーチャスペックに置き換えようとしていましたが、新たにE2Eテストを書くのであれば、システムスペックを使う方がいいでしょう(今後フィーチャスペックはシステムスペックに置き換わっていくため)。
というわけで、この記事ではシステムスペックで置き換えることにします。

システムスペック(またはフィーチャスペック)を書く場合は、「ブラウザを操作するユーザーの気持ち」になってテストを書くことが重要です。
基本的な考え方としては、「システムスペックはブラウザ上でできることにフォーカスしてテストを書く(やむを得ない場合のみ、サーバーサイドの処理やデータを覗き見る)」です。
ですので、session変数を直接読み書きするようなテストコードは通常書きません。

以下は先ほどのテストコードをフィーチャスペックで置き換える例です。

users_signup_spec.rb
require "rails_helper"

RSpec.describe "Users signup", type: :system do
  example "valid signup information" do
    # ユーザー登録画面を開く
    visit signup_path

    # 必要事項をフォームに入力する
    fill_in "Name", with: "Example User"
    fill_in "Email", with: "user@example.com"
    fill_in "Password", with: "password"
    fill_in "Confirmation", with: "password"

    # 登録ボタンをクリックすると、Userが1件増える
    expect {
      click_button "Create my account"
    }.to change(User, :count).by(1)

    # ユーザー詳細画面に遷移する
    user = User.last
    expect(current_path).to eq user_path(user)
    
    # Welcomeメッセージが表示される
    expect(page).to have_content "Welcome to the Sample App!"

    # ナビゲーションバー内の表示が"Log in"から"Account"に切り替わる
    within '.navbar-nav' do
      expect(page).to_not have_content 'Log in'
      expect(page).to have_content 'Account'
    end
  end
end

ここでは、ログインが完了したことを検証するために、「ナビゲーションバー内の表示が"Log in"から"Account"に切り替わる」を検証することにしました。
(システムスペック上ではsession変数の中身は意識しません)

# ナビゲーションバー内の表示が"Log in"から"Account"に切り替わる
within '.navbar-nav' do
  expect(page).to_not have_content 'Log in'
  expect(page).to have_content 'Account'
end

繰り返しになりますが、統合テストをシステムスペックに移行する場合は、「統合テスト内で確認していた内容を、ブラウザ上ではどう検証するか」を考える必要があります。
この翻訳作業が初心者さんにとっては、ちょっと難しい作業になるんじゃないかと僕は考えています。

参考情報

今回使用したサンプルコードはGitHubにアップしています。
以下のURLでdiffを確認できます。

また、解説動画もYouTubeにアップしています。
こっちの方が説明が詳しいと思うので、動画もぜひチェックしてみてください。

https://www.youtube.com/watch?v=-PAyalRKVW8
image

フィーチャスペックやシステムスペックの書き方については(そしてリクエストスペックも!)、電子書籍「Everyday Rails - RSpecによるRailsテスト入門」を読んで勉強するのがオススメです。

フィーチャスペックやシステムスペックでブラウザ上の操作をどうやって再現するかについては、以下のQiita記事も参考になります。

使えるRSpec入門・その4「どんなブラウザ操作も自由自在!逆引きCapybara大辞典」 - Qiita

88
88
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
88
88

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?