Ruby
Rails
RSpec
FactoryGirl
テスト

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

テスト対象

該当するコントローラ 該当するアクション
UsersController edit, update

環境

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

フォルダ、使用ファイル

種類 ファイル名
スペック spec/features/users_edit_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/2

users_edit_spec.rb
# spec/features/users_edit_spec.rb

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

  # 未ログイン場合のスペックは、
  # authorization_spec.rb にて(別ファイル)作成

  describe "edit"
    # 情報が valid の場合
    context "valid info"
      scenario "success edit profile"
    # 情報が invalid の場合
    context "invalid info"
      scenario "fail edit profile"

end

スペック作成 1/2

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

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

  include SupportModule
  include_context "setup"

  subject { page }

  describe "edit" do
    # 情報が valid の場合
    context "valid info" do
      scenario "success edit profile" do
        login_as(user)
        click_link "Settings"
        should have_title("Edit user")
        should have_css("h1", text: "Update your profile")
        should have_link("change", href: "http://gravatar.com/emails")
        expect {
          fill_in_update_profile_form("New Name", "new@example.com")
          click_button "Save changes"
        }.to change(User, :count).by(0)
        expect(user.reload.name).to eq "New Name"
        expect(user.reload.email).to eq "new@example.com"
        success_messages("Profile updated")
      end
    end
    # 情報が invalid の場合
    context "invalid info" do
      scenario "fail edit profile" do
        login_as(user)
        click_link "Settings"
        expect {
          fill_in_update_profile_form("", "foo@")
          click_button "Save changes"
        }.to change(User, :count).by(0)
        expect(user.reload.name).not_to eq ""
        expect(user.reload.email).not_to eq "foo@"
        error_messages("errors")
      end
    end
  end
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
    end
  end

SupportModule の作成

↓の部分をメソッドとして定義

  • fill_in_update_profile_form
  • success_messages("Profile updated")
  • error_messages("errors")
support_module.rb
  # spec/support/support_module.rb
  module SupportModule

    def fill_in_update_profile_form(name, email, password = "", confirmation = "")
      fill_in "Name",         with: name
      fill_in "Email",        with: email
      fill_in "Password",     with: password
      fill_in "Confirmation", with: confirmation
    end

    def success_messages(msg)
      expect(page).to have_css("div.alert.alert-success", text: msg)
    end

    def error_messages(msg = "")
      if msg.empty?
        should have_css('div.alert.alert-danger')
      else
        should have_css('div.alert.alert-danger', text: msg)
      end
    end
  end

実行結果 1/2

$ bin/rspec spec/features/users_edit_spec.rb -e "info"

UsersEdit
  edit
    valid info
      success edit profile
    invalid info
      fail edit profile

Finished in 4.96 seconds (files took 2.3 seconds to load)
2 examples, 0 failures


アウトラインの作成 2/2

users_edit_spec.rb
# spec/features/users_edit_spec.rb
RSpec.feature "UsersEdit", type: :feature do

  # 未ログイン場合のスペックは、
  # authorization_spec.rb にて(別ファイル)作成

  describe "edit"

    # (省略)

    # admin属性
    describe "admin-attribute"
      # admin属性をweb経由で変更できないこと
      scenario "not allow change via the web"
    # フレンドリーフォワーディング
    describe "friendly forwarding"
      # ログイン後は、
      context "after login (non-login-user)"
        # 目的としていたページに遷移していること
        scenario "render desired page"
end

スペック作成 2/2

  • patch user_path(user), params: { user: admin_params } の部分は、直接HTTPリクエストを送るので、オプション type: :request をつけないと NoMethodError になってしまう
users_edit_spec.rb
# spec/features/users_edit_spec.rb
RSpec.feature "UsersEdit", type: :feature do

  describe "edit"

    # (省略)

    # admin属性
    describe "admin-attribute", type: :request do
      # web経由で変更できないこと
      scenario "not allow change via the web" do
        patch user_path(user), params: { user: admin_params }
        expect(user.reload).not_to be_admin
        should have_current_path("/")
      end
    end

    # フレンドリーフォワーディング
    describe "friendly forwarding" do
      # ログイン後は、
      context "after login (non-login-user)" do
        # 目的としていたページに遷移していること
        scenario "render desired page" do
          visit edit_user_path(user)
          error_messages("Please log in")
          should have_current_path("/login")
          login_as(user)
          should have_current_path(edit_user_path(user))
          should have_title("Edit user")
          should have_css("h1", text: "Update your profile")
        end
      end
    end
  end
end

shared_context の作成

shared_context.rb
  # spec/support/shared_context.rb
  RSpec.shared_context "setup" do

    # 属性をハッシュ化して呼ばれた時に使う
    let(:admin_params) { attributes_for(:user, admin: true) }

  end

ファクトリの使い方

FactoryBot.attributes_for(:user, admin: true)
  • 属性値をハッシュ化する
  $ rails console test --sandbox

  [1] pry(main)>
  [2] pry(main)> params = FactoryBot.attributes_for(:user, admin: true)
  => {:name=>"Example user",
   :email=>"user@example.com",
   :password=>"foobar",
   :password_confirmation=>"foobar",
   :admin=>true}

実行結果 2/2

$ bin/rspec spec/features/users_edit_spec.rb -e "admin-attribute"

UsersEdit
  edit
    admin-attribute
      not allow change via the web

Finished in 1.58 seconds (files took 2.02 seconds to load)
1 example, 0 failures

$ bin/rspec spec/features/users_edit_spec.rb -e "friendly forwarding"

UsersEdit
  edit
    friendly forwarding
      after login (non-login-user)
        render desired page

Finished in 3.96 seconds (files took 2.09 seconds to load)
1 example, 0 failures


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

  • authorization_spec.rb (別ファイル)にて作成

アウトラインの作成

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 "edit"
          # ログインメッセージが出力されること
          it "should have error_messages 'Please log in'"
          # ログインページへリダイレクトされること
          it "should have redirect to '/login'"
        describe "update"
          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_expamples.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

スペック作成

authorization_spec.rb
# spec/features/authorization_spec.rb
RSpec.feature "Authorization", type: :feature do

  include SupportModule
  include_context "setup"

  # アクセス権限
  describe "in UsersController", type: :request do
    # 未ログインの場合 (before_action のテスト)
    describe "login is necessary" do
      context "when non-login" do
        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
      end
    end
  end
end

実行結果

$ bin/rspec spec/features/authorization_spec.rb -e "edit" -e "update"

Authorization
  in UsersController
    login is necessary
      when non-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"

Finished in 2.11 seconds (files took 2.38 seconds to load)
4 examples, 0 failures


参考


続く

フィーチャスペック編 users_follow_spec 6/7