fv17の日記

Webエンジニアの備忘用ブログです。主にWeb界隈の技術に関して書いています。

RSpec - フィーチャスペックを導入する

everyday Rails の「第6章 フィーチャスペックでUIをテストする」の復習用まとめ。
書籍から内容の増減があります。

本章の前提

フィーチャースペックとは、モデルとコントローラが 他の モデルやコントローラとうまく一緒に動作することを確認するテストのこと。受入テストや統合テストと呼ばれることもある。

目次

  • Capybaraをインストールする
  • フィーチャスペックの基本
  • CapybaraのDSL
  • セレクタのスコープを制限する
  • フィーチャスペックをデバッグする
  • JavaScript を使った操作をテストする
  • ヘッドレスドライバを使う
  • JavaScript の完了待ち時間を伸ばす

Capybaraをインストールする

Rails 5.1以降であれば標準でインストールされている。
5.0以前の場合はgemの指定が必要。

group :test do
  gem 'capybara', '~> 2.15.2'
end

テストスイートでCapybaraを読み込む設定

spec/rails_helper.rb

# ...
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!
require 'capybara/rspec'  # ←追加する
# ...

フィーチャスペックの基本

スペック作成

bin/rails generate rspec:feature projects

ログインからプロジェクト追加までをテスト

spec/features/projects_spec.rb

scenario " user creates a new project" do
  user = FactoryBot.create(:user)
  visit root_path
  click_link "Sign in"
  fill_in "Email", with: user.email
  fill_in "Password", with: user.password
  click_button "Log in"

  expect {
    click_link "New Project"
    fill_in "Name", with: "Test Project"
    fill_in "Description", with: "Trying out Capybara"
    click_button "Create Project"

    # click_buttonは起動されたアクション完了前に次の処理へ移ることがある
    # そのため、expect {} 内部でテストして完了を待ってから、countチェック
    expect(page).to have_content "Project was successfully created."
    expect(page).to have_content "Test Project"
    expect(page).to have_content "Owner: #{user.name}"
  }.to change(user.projects, :count).by(1)
end

CapybaraのDSL

簡易DSL

scenario"workswithallkindsofHTMLelements"do
  # ページを開く
  visit "/fake/page"
  # リンクまたはボタンのラベルをクリックする
  click_on "A link or button label"
  # チェックボックスのラベルをチェックする
  check "A checkbox label"
  # チェックボックスのラベルのチェックを外す
  uncheck "A checkbox label"
  # ラジオボタンのラベルを選択する
  choose "A radio button label"
  # セレクトメニューからオプションを選択する
  select "An option", from: "A select menu"
  # ファイルアップロードのラベルでファイルを添付する
  attach_file "A file upload label", "/some/file/in/my/test/suite.gif"
  # 指定した CSS に一致する要素が存在することを検証する
  expect(page).to have_css "hsubheading"
  # 指定したセレクタに一致する要素が存在することを検証する
  expect(page).to have_selector "ul li"
  # 現在のパスが指定されたパスであることを検証する
  expect(page).to have_current_path "/projects/new" 
end

セレクタのスコープを制限する

<div id="node">
  <a href="http://nodejs.org">click here!</a>
</div>
<div id="rails">
  <a href="http://rubyonrails.org">click here!</a>
</div>

下のclick here!をクリックしたい場合

within "#rails" do
  click_link "click here!"
end

特定の要素を検索して操作する

# 要素を指定して値を取得
language = find_field("Programming language").value
expect(language).to eq "Ruby"

# 要素を指定して操作
find("#fine_print").find("#disclaimer").click
find_button("Publish").click

フィーチャスペックをデバッグする

save_and_open_pageを置くことで、
tmp/capybara以下にresponseのhtmlファイルが吐き出される

scenario "guest adds a project" do
  visit projects_path
  save_and_open_page
  click_link "New Project"
end

デバッグ作業が終わったら、save_and_open_pageメソッドを削除する

自動で開きたい場合はLaunchy Gemを追加する

Gemfile

group:test do
  gem 'capybara', '~> 2.15.2'
  gem 'launchy', '~> 2.4.3'
end

JavaScript を使った操作をテストする

JSに依存するテストをする場合は下記の対応が必要。

  • js: true というオプションを渡す。
  • selenium-webdriver gemを使う。(Rails 5.1でデ􏰁ォルトでインストール)
  • Capybaraがselenium-webdriverに対して、Chromeを使うように設定。

とりあえず実装例を見て、設定は後述。

scenario "user toggles a task", js: true do
  user = FactoryBot.create(:user)
  project = FactoryBot.create(:project,
    name: "Rspec tutorial",
    owner: user)
  task = project.tasks.create!(name: "Finish Rspec tutorial")

  visit root_path
  click_link "Sign in"
  fill_in "Email", with: user.email
  fill_in "Password", with: user.password
  click_button "Log in"

  click_link "Rspec tutorial"
  check "Finish Rspec tutorial"

  expect(page).to have_css "label#task_#{task.id}.completed"
  expect(task.reload).to be_completed

  uncheck "Finish Rspec tutorial"

  expect(page).to_not have_css "label#task_#{task.id}.completed"
  expect(task.reload).to_not be_completed
end

Capybaraがselenium-webdriverでChromeを使うように設定

RSpec関連の設定ファイルをspec/supportディレクトリに配置できるようにする

spec/rails_helper.rb
の下記該当部分のコメントを外す

Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
Capybaraの設定

spec/support/capybara.rb

Capybara.javascript_driver = :selenium_chrome
ChromeDriverのインストール

Chrome とやりとりするインターフェース

group :test do
  gem 'capybara', '~> 2.15.4'  # 2.13.x系だとエラー出るので注意!
  gem 'selenium-webdriver'
  gem 'chromedriver-helper'
  gem 'launchy', '~> 2.4.3'
end

ヘッドレスドライバを使う

Circle CIやJenkinsでのテスト時にブラウザが起動するのを避けたい。

解決策1 - Chrome のヘッドレスモードを使う(version 59.0以上)

spec/support/capybara.rb

# Capybara.javascript_driver = :selenium_chrome  # ←削除
Capybara.javascript_driver = :selenium_chrome_headless  # ←追加

解決策2 - PhantomJSを使う

略。Chromeが使えない状況での代替案。
今後はChromeにシフトすると公式でアナウンスされている。

最新情報は公式docで

https://github.com/teamcapybara/capybara

JavaScript の完了待ち時間を伸ばす

Capybaraはデフォルトでボタン出現まで2秒待つ
これを長くする方法は2つ

※処理の完了を待つためにRubyのsleepメソッドはNG

設定ファイルに記述

ただし、テスト全体が遅くなるリスクがあるので注意
pec/support/capybara.rb

Capybara.default_max_wait_time = 15

スペックで実装時に指定

scenario "run sareally slow process" do
  using_wait_time(15) do
    # ここにテスト実装
  end
end