はじめに
フィーチャースペックを書いていると、ユーザー1人による操作ではなく、2人以上のユーザーを切り替えながら操作しないと、テストとして不十分な場合があります。
ただ、ユーザーを切り替えるたびにログインとログアウトの処理を毎回書くのは面倒です。
そんな場合は毎回ログイン・ログアウトの処理を書かなくて済むようなマクロ(ヘルパーメソッド)を定義しておくと便利です。
マクロを使わない例
たとえば掲示板サービスを開発していたとします。
あなたはこのサービスで以下のようなテストを書きたいと考えました。
- ユーザーAでログインする
- ユーザーAが親トピックを投稿する
- ユーザーBでログインする
- ユーザーBが2で作成したトピックに返信する
マクロを使わない場合、次のようなフィーチャースペックになります。
# spec/factories/users.rb
FactoryGirl.define do
factory(:user) do
sequence(:email) { |n| "user-#{n}@example.com" }
password '12345678'
end
end
# spec/features/message_board_spec.rb
require 'rails_helper'
feature '掲示板' do
given(:alice) { create :user }
given(:bob) { create :user }
scenario 'トピックの投稿とコメントの返信' do
# aliceとして操作
visit sign_in_path
fill_in 'email', with: alice.email
fill_in 'password', with: '12345678'
click_button 'ログイン'
expect(page).to have_content 'ログインしました。'
visit root_path
fill_in '投稿内容', with: '59年製ギブソンレスポールを3万円で譲ります。'
click_button '投稿する'
expect(page).to have_content '投稿しました。'
click_link 'ログアウト'
expect(page).to have_content 'ログアウトしました。'
# bobとして操作
visit sign_in_path
fill_in 'email', with: bob.email
fill_in 'password', with: '12345678'
click_button 'ログイン'
expect(page).to have_content 'ログインしました。'
visit root_path
fill_in 'コメント', with: 'はいはいはい!!!ほしいです!!!!'
click_button '返信する'
expect(page).to have_content '返信しました。'
end
end
マクロを使う例・基本編
しかし、上のコードはログインとログアウトの処理が冗長です。
そんな場合はマクロを定義するとテストコードがシンプルになります。
まず、以下のようにしてログインとログアウトを実行するマクロを使えるようにします。
# spec/support/login_macros.rb
module LoginMacros
def login(user)
visit sign_in_path
fill_in 'email', with: user.email
fill_in 'password', with: '12345678'
click_button 'ログイン'
expect(page).to have_content 'ログインしました。'
end
def logout
click_link 'ログアウト'
expect(page).to have_content 'ログアウトしました。'
end
end
# spec/rails_helper.rb
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
RSpec.configure do |config|
config.include LoginMacros
end
このマクロを使うと先ほどのフィーチャースペックはこうなります。
# spec/features/message_board_spec.rb
feature '掲示板' do
given(:alice) { create :user }
given(:bob) { create :user }
scenario 'トピックの投稿とコメントの返信' do
# aliceとして操作
login(alice)
visit root_path
fill_in '投稿内容', with: '59年製ギブソンレスポールを3万円で譲ります。'
click_button '投稿する'
expect(page).to have_content '投稿しました。'
logout
# bobとして操作
login(bob)
visit root_path
fill_in 'コメント', with: 'はいはいはい!!!ほしいです!!!!'
click_button '返信する'
expect(page).to have_content '返信しました。'
end
end
先ほどよりもテストコードがシンプルになりましたね。
マクロを使う例・発展編
しかし、login
、logout
すら毎回書くのは面倒です。
そこでもっと簡単にユーザーを切り替えるために、次のようなマクロを追加します。
# spec/support/login_macros.rb
module LoginMacros
# 省略
# ログイン・ログアウトの手順を省略するマクロ
def act_as(user)
login user
yield
logout
end
end
このact_as
メソッドは「ログイン => ブロックで書いた処理を実行(yield
) => ログアウト」を実行してくれます。
以下はact_as
メソッドの使用例です。
act_as alice do
visit root_path
# その他の処理
end
act_as
メソッドを先ほどのテストコードに組み込んでみましょう。
# spec/features/message_board_spec.rb
feature '掲示板' do
given(:alice) { create :user }
given(:bob) { create :user }
scenario 'トピックの投稿とコメントの返信' do
# aliceとして操作
act_as alice do
visit root_path
fill_in '投稿内容', with: '59年製ギブソンレスポールを3万円で譲ります。'
click_button '投稿する'
expect(page).to have_content '投稿しました。'
end
# bobとして操作
act_as bob do
visit root_path
fill_in 'コメント', with: 'はいはいはい!!!ほしいです!!!!'
click_button '返信する'
expect(page).to have_content '返信しました。'
end
end
end
ご覧のとおり、テストコードから冗長なログイン・ログアウトの処理を無くすことができました。
そればかりでなく、aliceの操作とbobの操作がブロックとしてインデントされるので、どの行がどちらのユーザーで操作しているのかもわかりやすくなっています。
まとめ
このように、毎回登場する面倒なコードはマクロとして定義しておくと便利です。
act_as
メソッドのようにブロックも活用すれば、さらに見通しのよいテストコードが書ける場合があります。
よかったら参考にしてみてください。
PR: 「Everyday Rails - RSpecによるRailsテスト入門」でRSpecを学習しましょう!
「RSpecでテストコードを書くのが苦手」という人は電子書籍「Everyday Rails - RSpecによるRailsテスト入門」がオススメです。
今回説明したようなマクロの定義も載っています。
まだ読んでいない方はぜひチェックしてみてください!
Everyday Rails - RSpecによるRailsテスト入門
ちなみに、2017年中にはRails 5 + RSpec 3.5版にアップデートする予定です。
詳しくはこちらのブログ記事をご覧ください。
【翻訳】Rails 5およびRSpec 3.5対応版「Everyday Rails - RSpecによるRailsテスト入門」のアップデートについて - give IT a try