テスト対象
該当するコントローラ | 該当するアクション |
---|---|
UsersController | index |
環境
フォルダ、使用ファイル
種類 | ファイル名 |
---|---|
スペック | spec/features/users_index_spec.rb |
スペック | spec/features/authorization_spec.rb |
サポートモジュール | spec/support/support_module.rb |
shared_context | spec/support/shared_context.rb |
shared_examples | spec/support/shared_examples.rb |
ファクトリ(ユーザ) | spec/support/factories/users.rb |
アウトラインの作成 1/3
ページネーション
※ 未ログインの場合(ページ保護)/ユーザ削除権限(admin権限)のテストは
authorization_spec.rb
(別ファイル)にて作成
フィーチャスペック編 authorization_spec
users_index_spec.rb
# spec/features/users_index_spec.rb
RSpec.feature "UsersIndex", type: :feature do
# 未ログイン場合のスペックは、
# authorization_spec.rb にて(別ファイル)作成
describe "index"
# ページネーション
describe "pagination"
# ページネーションでユーザが表示されること
scenario "list each user"
end
スペック作成 1/3
users_index_spec.rb
# spec/features/users_index_spec.rb
RSpec.feature "UsersIndex", type: :feature do
include SupportModule
include_context "setup"
subject { page }
describe "index" do
# ページネーション
describe "pagination" do
# (ファクトリをセット)
before { users } # => shared_context 内で定義
# (セットアップの確認)
it { expect(User.count).to eq users.count }
# ページネーションでユーザが表示されること
scenario "list each user" do
login_as(user) # => SupportModule 内で定義
click_link "Users"
should have_current_path("/users")
should have_title("All users")
should have_css("h1", text: "All users")
User.paginate(page: 1).each do |user|
expect(page).to have_css("li", text: user.name)
end
end
end
end
end
shared_context "setup"の作成、ファクトリの作成
-
before { users }
の部分
shared_context.rb
# spec/support/shared_context.rb
RSpec.shared_context "setup" do
# 遅延評価、呼ばれた時にDB保存される
let(:users) { create_list(:other_user, 30) }
end
users.rb
# spec/factories/users.rb
FactoryBot.define do
# 自分
# factory [ファクトリ名], class: [クラス名]
factory :user, class: User do
name "Example user"
email "user@example.com"
password "foobar"
password_confirmation "foobar"
admin false
# 他人
factory :other_user do
name { Faker::Name.name }
email { Faker::Internet.email }
end
end
end
SupportModule 作成
-
login_as(user)
の部分
support_module.rb
# spec/support/support_module.rb
module SupportModule
def login_as(user)
visit root_path
click_link "Log in"
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Log in"
end
end
実行結果 1/3
$ bin/rspec spec/features/users_index_spec.rb
UsersIndex
index
pagination
should eq 30
list each user
Finished in 5.22 seconds (files took 2.1 seconds to load)
2 examples, 0 failures
未ログインの場合(ページ保護)のスペック
- authorization_spec.rb (別ファイル)にて作成
アウトラインの作成 2/3
authorization_spec.rb
# spec/features/authorization_spec.rb
RSpec.feature "Authorization", type: :feature do
# ページ保護(アクセス権限)
describe "in UsersController"
describe "login is necessary"
# 未ログインの場合
context "when non-login"
describe "index"
# ログインメッセージが出力されること
it "should have error_messages 'Please log in'"
# ログインページへリダイレクトされること
it "should have redirect to '/login'"
end
shared_examples の作成
ログインメッセージ出力・ログインページへリダイレクトは、一般化して使い回せるようにしてみる
spec側でブロックをオブジェクト化(
Proc.new { }
)してsubject { }
に定義し、shared_examples側で、subject.call
して呼び出す
shared_examples.rb
# spec/support/shared_examples.rb
# 成功メッセージ
# flash[:success]
shared_examples_for "success message" do |msg|
it { subject.call; expect(flash[:success]).to eq msg }
end
# 失敗メッセージ
# flash[:danger]
shared_examples_for "error message" do |msg|
it { subject.call; expect(flash[:danger]).to eq msg }
end
# リダイレクト
# redirect to path
shared_examples_for "redirect to path" do |path|
it { subject.call; expect(response).to redirect_to path }
end
スペック作成 2/3
- spec側でブロックをオブジェクト化(
Proc.new { }
)してsubject { }
に定義 -
get users_path
の部分は、直接HTTPリクエストを送るので、オプションtype: :request
をつけないとNoMethodError
になってしまう
authorization_spec.rb
# spec/features/authorization_spec.rb
require 'rails_helper'
RSpec.feature "Authorization", type: :feature do
describe "in UsersController", type: :request do
describe "login is necessary" do
context "when non-login" do
describe "index" do
subject { Proc.new { get users_path } }
it_behaves_like "error flash", "Please log in"
it_behaves_like "redirect to path", "/login"
end
end
end
end
end
実行結果 2/3
$ bin/rspec spec/features/authorization_spec.rb -e "index"
Authorization
in UsersController
login is necessary
when non-login
index
behaves like error message
should eq "Please log in"
behaves like redirect to path
should redirect to "/login"
Finished in 1.64 seconds (files took 2.31 seconds to load)
2 examples, 0 failures
ユーザ削除権限(admin権限)のスペック
- authorization_spec.rb (別ファイル)にて作成
アウトライン作成 3/3
authorization_spec.rb
# spec/features/authorization_spec.rb
RSpec.feature "Authorization", type: :feature do
describe "in UsersController", type: :request do
# (省略)
# ユーザ削除権限
describe "authorization of delete user"
# adminユーザの場合
context "admin"
# ユーザの削除ができること
it "success create user"
# 成功メッセージ
it "have success messages"
# 一般ユーザの場合
context "non-admin"
# ユーザの削除ができないこと
it "fail delete user"
# ルートにリダイレクトしていること
it "have current path '/'"
end
スペック作成 3/3
authorization_spec.rb
# spec/features/authorization_spec.rb
RSpec.feature "Authorization", type: :feature do
include SupportModule # => login_as(admin) を使う
include_context "setup"
describe "in UsersController", type: :request do
# (省略)
# ユーザ削除権限
describe "authorization of delete user" do
before { users }
it { expect(User.count).to eq users.count } # (セットアップ確認)
# adminユーザの場合
context "admin" do
it_behaves_like "success delete user"
end
# 一般ユーザの場合
context "non-admin" do
it_behaves_like "fail delete user"
it { should have_current_path("/") }
end
end
end
end
shared_examples の作成
-
it_behaves_like "success delete user"
の部分を作成 -
it_behaves_like "fail delete user"
の部分を作成
shared_examples.rb
# spec/support/shared_examples.rb
# ユーザの削除(admin権限)
# users#destroy
# 成功
shared_examples_for "success delete user" do
scenario "user decrememt -1" do
login_as(admin)
click_link "Users"
expect(page).to have_current_path("/users")
expect(page).to have_link('delete', href: user_path(User.first))
expect(page).to have_link('delete', href: user_path(User.second))
expect(page).not_to have_link('delete', href: user_path(admin))
expect {
click_link('delete', match: :first)
# 成功メッセージ
expect(page).to have_css("div.alert.alert-success", text: "User deleted")
}.to change(User, :count).by(-1)
end
end
# 失敗
shared_examples_for "fail delete user" do
scenario "user decrement 0" do
login_as(user)
click_link "Users"
expect(page).to have_current_path("/users")
expect(page).not_to have_link('delete', href: user_path(User.first))
expect(page).not_to have_link('delete', href: user_path(User.second))
expect {
# リンクが無いので、直接 HTTPリクエストを発行
delete user_path(User.first)
}.to change(User, :count).by(0)
end
end
実行結果 3/3
$ bin/rspec spec/features/authorization_spec.rb -e "authorization of delete user"
Authorization
in UsersController
authorization of delete user
should eq 30
admin
behaves like success delete user
user decrememt -1
non-admin
should have current path "/"
behaves like fail delete user
user decrement 0
Finished in 8.17 seconds (files took 2.23 seconds to load)
4 examples, 0 failures