LoginSignup
5
3

More than 5 years have passed since last update.

Railsチュートリアル テストをRSpecで実施 【フィーチャスペック編 authorization_spec】

Last updated at Posted at 2018-08-10

テスト対象

該当するコントローラ 該当するアクション
UsersController index, create, edit, update, destroy, following, followers
MicropostsController create, destroy

環境

Userモデル 単体テスト編 1/3 に記載

フォルダ、使用ファイル

種類 ファイル名
スペック 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
ファクトリ(マイクロポスト) spec/support/factories/microposts.rb

未ログインの場合(ページ保護)のスペック

アウトラインの作成

authorization_spec.rb
# spec/features/authorization_spec.rb
RSpec.feature "Authorization", type: :feature do
  # アクセス権限
  describe "in UsersController"
    # 未ログインの場合 (before_action のテスト)
    describe "login is necessary"
      context "when non-login"
        describe "index"
          # エラーメッセージ、ログインページにリダイレクト
        describe "edit"
          # エラーメッセージ、ログインページにリダイレクト
        describe "update"
          # エラーメッセージ、ログインページにリダイレクト
        describe "destroy"
          # エラーメッセージ、ログインページにリダイレクト
        describe "following"
          # エラーメッセージ、ログインページにリダイレクト
        describe "followers"
          # エラーメッセージ、ログインページにリダイレクト
    # ユーザ削除権限
    describe "authorization of delete user"
      # adminユーザの場合
      context "admin"
        # ユーザの削除ができること
        # 成功メッセージ
      # 一般ユーザの場合
      context "non-admin"
        # ユーザの削除ができないこと
        # ルートにリダイレクトされること

  describe "in MicropostsController"
    # マイクロポスト 投稿(作成)/削除
    describe "authorization of create/destroy micropost"
      context "when non-login"
        describe "create"
          # エラーメッセージ、ログインページにリダイレクト
        describe "destroy"
          # エラーメッセージ、ログインページにリダイレクト
end

shared_examples の作成 1/2

  • ログインメッセージ出力・ログインページへリダイレクトは、一般化して使い回せるようにしてみる

  • 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

shared_examples の作成 2/2

↓のように、ユーザの削除(admin権限)成功/失敗のスペックを作成

  • 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

スペック作成

authorization_spec.rb
# spec/features/authorization_spec.rb
require 'rails_helper'

RSpec.feature "Authorization", type: :feature do

  include SupportModule
  include_context "setup"

  # アクセス権限
  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
        describe "edit" do
          subject { Proc.new { get edit_user_path(user) } }
          it_behaves_like "error flash", "Please log in"
          it_behaves_like "redirect to path", "/login"
        end
        describe "update" do
          subject { Proc.new { patch user_path(user), params: { user: update_params_1 } } }
          it_behaves_like "error flash", "Please log in"
          it_behaves_like "redirect to path", "/login"
        end
        describe "destroy" do
          subject { Proc.new { delete user_path(other_user) } }
          it_behaves_like "error flash", "Please log in"
          it_behaves_like "redirect to path", "/login"
        end
        describe "following" do
          subject { Proc.new { get following_user_path(user) } }
          it_behaves_like "error flash", "Please log in"
          it_behaves_like "redirect to path", "/login"
        end
        describe "followers" do
          subject { Proc.new { get followers_user_path(user) } }
          it_behaves_like "error flash", "Please log in"
          it_behaves_like "redirect to path", "/login"
        end
      end
    end
    # ユーザ削除権限
    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 { expect(page).to have_current_path("/") }
      end
    end
  end

  describe "in MicropostsController", type: :request do
    # マイクロポスト 投稿(作成)/削除
    describe "authorization of create/destroy micropost" do
      context "when non-login" do
        describe "create" do
          subject { Proc.new { post microposts_path, params: post_params } }
          it_behaves_like "error flash", "Please log in"
          it_behaves_like "redirect to path", "/login"
        end
        describe "destroy" do
          subject { Proc.new { delete micropost_path(my_post.id) } }
          it_behaves_like "error flash", "Please log in"
          it_behaves_like "redirect to path", "/login"
        end
      end
    end
  end
end

SupportModule, shared_context, ファクトリ

  • SupportModule
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
  • shared_context
shared_context.rb
  # spec/support/shared_context.rb
  RSpec.shared_context "setup" do
    # ユーザ
    # 遅延評価、呼ばれた時にDB保存される
    let(:user) { create(:user) }
    let(:admin) { create(:admin) }

    # マイクロポスト
    # 自分の投稿
    let(:my_post) { create(:user_post) }
    # 属性をハッシュ化して呼ばれた時に使う
    let(:post_params) { attributes_for(:user_post) }
  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 }
        # 管理者ユーザ
        factory :admin do
          admin true
        end
      end
    end
  end
microposts.rb
  # spec/factories/microposts.rb
  FactoryBot.define do
    # 自分のマイクロポスト
    factory :user_post, class: Micropost do
      content { Faker::Lorem.sentence(5) }
      association :user, factory: :user
      # 他人のマイクロポスト
      factory :other_user_post do
        content { Faker::Lorem.sentence(5) }
        association :user, factory: :other_user
      end
    end
  end

実行結果

$ bin/rspec spec/features/authorization_spec.rb
Running via Spring preloader in process 17859

Authorization
  in UsersController
    login is necessary
      when non-login
        index
          behaves like error flash
            should eq "Please log in"
          behaves like redirect to path
            should redirect to "/login"
        edit
          behaves like error flash
            should eq "Please log in"
          behaves like redirect to path
            should redirect to "/login"
        update
          behaves like error flash
            should eq "Please log in"
          behaves like redirect to path
            should redirect to "/login"
        destroy
          behaves like error flash
            should eq "Please log in"
          behaves like redirect to path
            should redirect to "/login"
        following
          behaves like error flash
            should eq "Please log in"
          behaves like redirect to path
            should redirect to "/login"
        followers
          behaves like error flash
            should eq "Please log in"
          behaves like redirect to path
            should redirect to "/login"
    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
  in MicropostsController
    authorization of create/destroy micropost
      when non-login
        create
          behaves like error flash
            should eq "Please log in"
          behaves like redirect to path
            should redirect to "/login"
        destroy
          behaves like error flash
            should eq "Please log in"
          behaves like redirect to path
            should redirect to "/login"

Finished in 13.41 seconds (files took 2.92 seconds to load)
20 examples, 0 failures


参考


以上

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3