RSpec 基本まとめ01(Model, FactoryBot, Controller)の続きです
フューチャスペックでUIをテスト
- 統合テストと呼ばれる。これまでの単体テストは違って、開発したソフトウェア全体がシステムとして動作するか検証
- Rails5.1ではシステムテストという名前でデフォルトでサポートされるようになったので、システムスペックへ移行することがお勧めされている(内容的にはほぼ同じ)
Capybara gem インストール
ブラウザの操作をシミュレートするために、Capybara gemを使用する。主に、リンクのクリック、フォームの入力、画面の表示を検証することができる
(Rails5.1からはCapybaraは既にインストールされているが、補足として記載)
group :test do
# selenium_chromeがデフォルト登録されたのがv2.15以降のためバージョンを指定
gem 'capybara', '~> 2.15.2'
end
また、CapybaraはRails開発環境で実行可能なジェネレータは用意されていないため、テスト環境にだけ追加することで、開発環境のメモリ消費を軽くすることができる。
また、bundleコマンド後、Capybaraを読み込むよう設定
.
.
require 'spec_helper'
require 'rspec/rails'
# 追加
require 'capybara/rspec'
.
ファイル生成
# 例
rails g rspec:feature posts
テスト例
require 'rails_helper'
RSpec.feature "Posts", type: :feature do
# 例:ユーザーがポストする
scenario "user creates a new project" do
user = FactoryBot.create(:user)
visit root_path
click_link "Sign in"
fill_in "Password", with: user.password
click_button "Log in"
expect {
click_link "New Post"
fill_in "Name", with: "Test Post"
click_button "Create Post"
expect(page).to have_content "Post was successfully created"
expect(page).to have_content "Test Post"
expect(page).to have_content "Owner: #{user.name}"
}.to change(user.posts, :count).by(1)
end
end
-
コントローラスペックとフューチャスペックの違いは、利用者と同じようにフォーム等を使って、画面操作でのテスト。コントローラスペックはUIを無視して、パラメータを直接コントローラのメソッドに送信する
-
また、フューチャスペックでは1つのexample、もしくは1つのシナリオで複数のエクスペクテーションを書くのは問題ではない。
-
scenario
:it
と同様に、example
の起点を表す。 -
visit
: ページを開く -
click_link
: リンクを押す -
fill_in ~~, with --
: フォームを入力。withの後に値を設定できる。 -
click_button
: ボタンをクリック。(起動されたアクションが完了する前に、次の処理へうつってしまうことがあるため、1個以上のエクスペクテーションを実行し、処理の完了を待つ) -
page
: ページの要素を取得 -
have_content
: 想定した文字列がページ内にあるか検証
他にも、よく使用されると考えられるのが、
-
check
: チェックボックスのラベルをチェック -
uncheck
: チェックボックスのラベルを外す -
choose
: ラジオボタンのラベルを選択する -
select ~~, from: --
: セレクトメニューからオプションを選択 -
attach_file
: ファイルアップロードのラベルでファイル添付 -
have_css
: 指定したcssに一致する要素があるか検証 -
have_selector
: 指定したセレクタに一致する要素があるか検証 -
have_current_path
: 現在のパスが指定したパスであるか検証
Capybara DSL ドキュメントで、他のメソッドも多く掲載されている。
フューチャスペックをデバッグ
Capybaraはヘッドレスブラウザ(UIを持たない)を使ってテストを実行するため、処理ステップを目で確認することができない。
ただ、save_and_open_page
メソッドを、テストが失敗する直前の箇所に記述することで、Railsがブラウザに返したHTMLを確認することができる
# ログインしていないことによるエラーを確認
scenario "guest adds a post" do
visit posts_path
save_and_open_page
# ログインしていないことで、別ページに遷移しており、該当リンクがないことでエラー
click_link "New Post"
end
こうすると、エラー内容の中に、ファイルのパスが表示され、そこからブラウザ上で確認することが出来る。(ログインしてないから、別のページにリダイレクトしており、ボタンがなかった!などが判明する)
また、Launchy
gem をインストールすることで、自動でブラウザに表示されるようになる。
save_and_open_page
メソッドは、デバッグ用のメソッドのため、不要になったら削除するのを忘れないように!
JavaScript込みの操作をテスト
この節の内容が、自分の開発環境ではまだ正常に動作していないため、後ほど更新します!💦
リクエストスペック
現在、コントローラスペックではなく、リクエストスペックが推奨されている。そのため、フィーチャスペックで使用した、Capybaraは使用しません。それは、ブラウザの操作を検証する訳ではないからです。代わりに、HTTPメソッド(GET, POST, DELETE, PATCH)を使用します。
ここでは、APIのテストに関して記述します。
ファイル作成
# 例
bin/rails g rspec:request posts_api
このコマンドで、spec/requests/posts_apis_spec.rb
ファイルが生成されるが、API
が複数形になり不自然なため、手動で変更します。
⇨ spec/requests/posts_api_spec.rb
テスト例
GETリクエスト
- 記述に関しては、コントローラスペックとほぼ変わりませんが、リクエストスペックは出来ることが増えます。
require 'rails_helper'
RSpec.describe "Posts Api", type: :request do
it "loads a post" do
user = FactoryBot.create(:user)
FactoryBot.create(:post,
name: "Sample Post"
)
FactoryBot.create(:post,
name: "Second Sample Post",
owner: user
)
get api_posts_path, params: {
# APIではユーザーのメアドとサインインするためのトークンが必要
user_email: user.email,
user_token: user.authentication_token
}
expect(response).to have_http_status(:success)
json = Json.parse(response.body)
expect(json.length).to eq 1
post_id = json[0]["id"]
# ownerを設定したpostのみ読み込まれるか検証
get api_post_path(post_id), params: {
user_email: user.email,
user_token: user.authentication_token
}
expect(response).to have_http_status(:success)
json = JSON.parse(response.body)
expect(json["name"]).to eq "Second Sample Post"
end
end
- コントローラスペックよりも、フューチャスペックぽさがある記述になります。
- コントローラスペックとは異なり、好きなルーティング名を何度でも使用することが出来る。リクエストスペックはコントローラに結びつかないため。そのため、テストしたいルーティング名をしっかり指定しているか確認することが必要。
POSTリクエスト
require 'rails_helper'
describe "Posts API", type: :request do
.
.
it "creates a post" do
user = FactoryBot.create(:user)
post_attributes = FactoryBot.attributes_for(:post)
# postリクエストを送り、postが1件作成されることを検証
expect {
post api_posts_path, params: {
user_email: user.email,
user_token: user.authentication_token,
# プロジェクトの属性も含める
post: post_attributes
}
}.to change(user.posts, :count).by(1)
expect(response).to have_http_status(:success)
end
end
コントローラスペックをリクエストスペックに置き換え
リクエストスペックが推奨されているので置き換えてみます。
.
before do
@user = FactoryBot.create(:user)
end
# 有効な属性値を渡し、post作成を検証
it "adds a post" do
post_params = FactoryBot.attributes_for(:post)
sign_in @user
expect {
post posts_path, params: { post: post_params }
}.to change(@user.posts, :count).by(1)
end
# 無効な属性値を渡し、postが作成されないことを検証
it "does not add a post" do
post_params = FactoryBot.create(:post, :invalid)
sign_in @user
expect {
post posts_path, params: { post: post_params }
}.to_not change(@user.posts, :count)
end
-
コントローラスペックとの違いは、Postコントローラのcreateアクションに直接依存するのではなく、具体的なルーティング名を指定してPOSTリクエストを送信する点。
-
また、Deviseの
sign_in
ヘルパーを使用するには、下記のように、リクエストスペックで使用することを明記します。
まずspec/support/request_spec_helper.rb
を作成
module RequestSpecHelper
include Warden::Test::Helpers
def self.included(base)
base.before(:each) { Warden.test_mode! }
base.after(:each) { Warden.test_reset! }
end
def sign_in(resource)
login_as(resource, scope: warden_scope(resource))
end
def sign_out(resource)
logout(warden_scope(resource))
end
private
def warden_scope(resource)
resource.class.name.underscore.to_sym
end
end
.
.
RSpec.configure do |config|
.
.
config.include Devise::Test::ControllerHelpers, type: :controller
# 追加
config.include RequestSpecHelper, type: :request
end