Rails: システムテストをRSpecで実行する(翻訳)
RSpec 3.7登場前のfeature spec は、実物(またはヘッドレス)のブラウザ環境でJavaScriptの絡むアプリのやり取りをフルスタックでテストする手段でした。最近リリースされたRSpec 3.7(訳注: 現在はRSpec 4.0です)では、Railsのシステムテストを元にしたsystem specが追加されました。Rails 5.1ではActionDispatch::SystemTestCase
が導入され、実際のブラウザでのテストに使えるようになりました。設定済みのCapybara
ラッパーが提供され、Railsフレームワークに組み込まれている多くの機能を利用できるようになりました。設定済みのCapybaraのおかげで、従来は正しく設定するためにトリッキーになりがちだった手動設定の手間が大きく軽減されました。feature specの代わりにsystem specを使うメリットは次のとおりです。
- テストが終わるとデータベースの変更が自動でロールバックされるので、database_cleaner gemを用いてロールバックを手動で設定する必要がない。
driven_by
を使うと、specごとにブラウザを簡単に切り替えられる。- テストが失敗すると即座にスクリーンショットを自動撮影し、ターミナルにもスクリーンショットをインライン表示する。この機能は事前設定済みなので、
capybara-screenshot
gemなどが不要になる。
上述のメリットに加えて、RSpecチームがRails 5.1以降ではfeature specよりもsystem specを推奨している点も強調しておきたいと思います。
🔗 RSpecのsystem specをヘッドレスChrome向けにセットアップする
まずはRailsプロジェクトを新規作成しましょう。いつものように--skip-test
オプションを追加して、RailsデフォルトのminitestではなくRSpecを使うようにします。
rails --version
Rails 5.2.0.beta2
rails new rspec-system-specs --skip-test --skip-active-storage
セットアップでGemfile
にrspec-rails
を追加する作業以外で最も面倒なのは、ヘッドレスChromeブラウザのテストに必要なgemを見極めることでしょう。必要なgemのリストを以下に示します。
group :development, :test do
# Capybara system testingとselenium driverのサポートを追加
gem 'capybara', '~> 2.16.1'
gem 'selenium-webdriver', '~> 3.8.0'
# Chromeでのシステムテスト実行に使うchromedriverを簡易インストール
gem 'chromedriver-helper', '~> 1.1.0'
gem 'rspec-rails', '~> 3.7.2'
end
chromedriver
がインストールされていることを確認します(訳注: 以下はMacでhomebrewを使う場合です)。
brew install chromedriver
chromedriver --version
#> ChromeDriver 2.34.522932 (4140ab217e1ca1bec0c4b4d1b148f3361eb3a03e)
以下を実行してspec/test_helper.rb
とspec/rails_helper.rb
を生成します。
rails g rspec:install
🔗 system specを書く
ここでは単純なhome#index
アクションがroutes.rb
のrootとして設定されていて、app/views/home/index.html.erb
で以下をレンダリングするとします。
<h1 id="title">Hello World</h1>
最初のsystem specを次のようにspec/system/home_spec.rb
に実装できます。
require 'rails_helper'
describe 'Homepage' do
before do
driven_by :selenium_chrome_headless
end
it 'shows greeting' do
visit root_url
expect(page).to have_content 'Hello World'
end
end
ここではヘッドレスChromeを使いたいので、:selenium_chrome_headless
ドライバを設定しています。Capybaraでこの他に提供されている登録済みドライバは、:rack_test
、:selenium
、:selenium_chrome
です。ブラウザの解像度などの高度な設定オプションについてのドキュメントは、driven_by
をご覧ください。
ちゃんと動くかどうか確認します。
$ rspec
Puma starting in single mode...
* Version 3.11.0 (ruby 2.5.0-p0), codename: Love Song
* Min threads: 0, max threads: 4
* Environment: test
* Listening on tcp://127.0.0.1:50713
Use Ctrl-C to stop
.
Finished in 1.41 seconds (files took 2.04 seconds to load)
1 example, 0 failures
個別のspecでドライバを設定したくない場合は、以下のようにspec_helper.rb
でデフォルトのグローバル設定を行なえます。
RSpec.configure do |config|
config.before(:each, type: :system) do
driven_by :selenium_chrome_headless
end
end
後は必要に応じて個別のspecファイルでデフォルトのドライバ設定を上書きします。
🔗 テスト失敗時のブラウザスクリーンショット
system specで特筆したい点は、specが失敗したときにブラウザのスクリーンショットを自動で撮影し、ターミナルにインライン出力してくれる便利な機能があることです。
この機能はRailsのシステムテストに組み込まれているので、feature specのようにcapybara-screenshot
などによるサポートを手動で設定する必要がありません。
🔗 JavaScriptのテスト
ここまではサーバー側でのコンテンツレンダリングのテストだけなので、今度はクライアント側のJavaScriptを追加して、JavaScriptが動くブラウザ(ここではヘッドレスChrome)がsystem specで使えることを示してみましょう。
$ ->
$('#title').text('Hello Universe')
specのアサーションをexpect(page).to have_content 'Hello Universe'
に変更すると、クライアント側でのJavaScript変更のspecテストはこれまで同様パスします。
$ rspec
Finished in 1.99 seconds (files took 2.44 seconds to load)
1 example, 0 failures
🔗 データベースの自動ロールバック
上述したように、system specでのデータベース変更は自動的にロールバックされます。ページにデータベース出力を少し追加してテストしてみましょう。
<h1 id="title">Hello World</h1>
<p>
Planet count: <%= Planet.count %>
</p>
specを変更してレコードをseedし、出力されたレコードのcount
のアサーションを行うspec exampleを新たに追加します。
describe 'Homepage' do
before do
driven_by :selenium_chrome_headless
puts 'creating a planet'
Planet.create!(name: 'Mars')
end
it 'shows greeting' do
visit root_url
expect(page).to have_content 'Hello Universe'
end
it 'shows planet count' do
visit root_url
expect(page).to have_content 'Planet count: 1'
end
end
rspec
の結果は次のようになります。
$ rspec --format d
creating a planet
shows greeting
# spec example追加後のデータベース自動ロールバック
creating a planet
shows planet count
Finished in 1.52 seconds (files took 1.03 seconds to load)
2 examples, 0 failures
できました!feature specではspec example実行後にデータベースの状態をクリーンアップするためにdatabase_cleaner
gemの設定が必要でしたが、これで不要になりました。
本記事のデモに用いた例のソースコードはGitHubに置いてあります。
概要
原著者の許諾を得て翻訳・公開いたします。
@jnchitoさんの以下の記事も合わせてどうぞ。