RailsチュートリアルのテストをRspecで書いてみた[第10章]

はじめに

今回はRspecの学習の一環として、RailsチュートリアルのテストをRspecで書いていきます。
至らない点があるかもしれませんが、その際はコメントにてご指摘をお願いします。

各種バージョン

Ruby 2.7.0
Rails 6.0.3.3
Rspec 3.9
Capybara 3.33.0
Factory_bot_rails 6.1.0

第10章

リスト10.9: 編集の失敗に対するテスト

今回はsystemスペックを使用しました。また、ログインする箇所をサポートモジュールに切り出しています。

spec/support/test_helper.rb
...
module SystemHelper
  def login_as(user)
    visit login_path
    fill_in 'Email',    with: user.email
    fill_in 'Password', with: user.password
    click_button 'Log in'
  end
end

RSpec.configure do |config|
  config.include TestHelper
  config.include SystemHelper  #<=  追加
end
spec/system/users_edit_spec.rb

require 'rails_helper'

RSpec.describe "UsersEdits", type: :system do
  let(:user) { FactoryBot.create(:user) }
  scenario 'it fails edit with wrong information' do
    login_as(user)
    click_on 'Setting'
    fill_in 'Name', with: ' '
    fill_in 'Email', with: 'user@invalid'
    fill_in 'Password', with: 'foo'
    fill_in 'Confirmation', with: 'bar'
    click_on 'Save changes'
    aggregate_failures do
      expect(current_path).to eq user_path(user)
      expect(has_css?('.alert-danger')).to be_truthy
    end
  end
end

リスト10.11: 編集の成功に対するテスト

spec/system/users_edit_spec.rb

require 'rails_helper'

RSpec.describe "UsersEdits", type: :system do
  let(:user) { FactoryBot.create(:user) }
#  一つ前のテストと重複するコードがあったので、before文に切り出しています。
  before do
    login_as(user)
    click_on 'Setting'
  end

  scenario 'it fails edit with wrong information' do
    fill_in 'Name', with: ' '
    fill_in 'Email', with: 'user@invalid'
    fill_in 'Password', with: 'foo'
    fill_in 'Confirmation', with: 'bar'
    click_on 'Save changes'
    aggregate_failures do
      expect(current_path).to eq user_path(user)
      expect(has_css?('.alert-danger')).to be_truthy
    end
  end

# ここからが追加したテストです。
  scenario 'it succeeds edit with correct information' do
    fill_in 'Name', with: 'Foo Bar'
    fill_in 'Email', with: 'foo@bar.com'
    fill_in 'Password', with: ''
    fill_in 'Confirmation', with: ''
    click_on 'Save changes'
    aggregate_failures do
      expect(current_path).to eq user_path(user)
      expect(has_css?('.alert-success')).to be_truthy
    end
  end
end

また、一応requestスペックも書きました。

spec/requests/users_request_spec.rb

require 'rails_helper'

RSpec.describe "Users", type: :request do
・・・
  describe "PATCH /users/:id" do
    let(:user) { FactoryBot.create(:user) }

    it 'succeeds edit with correct information' do
      patch user_path(user), params: { user: {
        name: "Foo Bar",
        email: "foo@bar.com",
        password: "",
        password_confirmation: "",
      } }
      expect(response).to redirect_to user_path(user)
    end
  end
end

リスト10.17: テストユーザーでログインする

spec/requests/users_request_spec.rb

require 'rails_helper'

RSpec.describe "Users", type: :request do
・・・
  describe "PATCH /users/:id" do
    let(:user) { FactoryBot.create(:user) }
    before { log_in_as(user) } #<= ここを追加しています。
    it 'fails edit with wrong information' do
      patch user_path(user), params: { user: {
        name: " ",
        email: "foo@invalid",
        password: "foo",
        password_confirmation: "bar",
      } }
      expect(response).to have_http_status(200)
    end

    it 'succeeds edit with correct information' do
      patch user_path(user), params: { user: {
        name: "Foo Bar",
        email: "foo@bar.com",
        password: "",
        password_confirmation: "",
      } }
      expect(response).to redirect_to user_path(user)
    end
  end
end

リスト10.20: editとupdateアクションの保護に対するテスト

spec/requests/users_request_spec.rb

require 'rails_helper'

RSpec.describe "Users", type: :request do
・・・
  describe "before_action: :logged_in_user" do
    let(:user) { FactoryBot.create(:user) }

    it 'redirects edit when not logged in' do
      get edit_user_path(user)
      expect(response).to redirect_to login_path
    end

    it 'redirects update when not logged in' do
      patch user_path(user), params: { user: {
        name: user.name,
        email: user.email,
      } }
      expect(response).to redirect_to login_path
    end
  end
end

リスト10.24: 間違ったユーザーが編集しようとしたときのテスト

spec/requests/users_request_spec.rb

require 'rails_helper'

RSpec.describe "Users", type: :request do
・・・
  describe "before_action: :correct_user" do
    let(:user) { FactoryBot.create(:user) }
    let(:other_user) { FactoryBot.create(:user) }

    before { log_in_as(other_user) }
    it 'redirects edit when logged in as wrong user' do
      get edit_user_path(user)
      expect(response).to redirect_to root_path
    end

    it 'redirects update when logged in as wrong user' do
      patch user_path(user), params: { user: {
        name: user.name,
        eemail: user.email,
      } }
      expect(response).to redirect_to root_path
    end
  end
end

リスト10.29: フレンドリーフォワーディングのテスト

spec/requests/sessions_request_spec.rb

require 'rails_helper'

RSpec.describe "Sessions", type: :request do
  let(:user) { FactoryBot.create(:user) }
・・・
  describe "friendly forwarding" do
    let(:user) { FactoryBot.create(:user) }
    it 'succeeds' do
      get edit_user_path(user)
      log_in_as(user)
      expect(response).to redirect_to edit_user_url(user)
    end
  end
end

リスト10.34: indexアクションのリダイレクトをテストする

spec/requests/users_request_spec.rb

require 'rails_helper'

RSpec.describe "Users", type: :request do
・・・
  describe "GET /users" do
    it "redirects login when not logged in" do
      get users_path
      expect(response).to redirect_to login_url
    end
  end
end

リスト10.61: 管理者権限の制御をアクションレベルでテストする

FactoryBotで新しく管理者権限をもつテストユーザを作成します。
また、「trait」を使用して重複部を省略しています。

spec/factories/users.rb

FactoryBot.define do
  factory :user do
    name { "TestUser" }
    sequence(:email) { |n| "test#{n}@example.com" }
    password { "foobar" }
    password_confirmation { "foobar" }

# ここから下の文を追加しています。
# FactoryBot.create(:user, :admin)の形で呼び出せます。
    trait :admin do
      admin { true }
    end
  end
spec/requests/users_request_spec.rb

require 'rails_helper'

RSpec.describe "Users", type: :request do
 ・・・

  describe "before_action: :logged_in_user" do
    let(:user) { FactoryBot.create(:user) }
 ・・・
    it 'redirects delete when not logged in' do
      delete user_path(user)
      expect(response).to redirect_to login_url
    end
  end
 ・・・

  describe "delete /users/:id" do
#「let!」を使用して遅延評価を打ち消しています。(before文と等価)
    let!(:user) { FactoryBot.create(:user) }
    let!(:admin_user) { FactoryBot.create(:user, :admin) }

    it 'fails when not admin' do
      log_in_as(user)
      aggregate_failures do
        expect do
          delete user_path(admin_user)
        end.to change(User, :count).by(0)
        expect(response).to redirect_to root_url
      end
    end

    it 'succeds when user is administrator' do
      log_in_as(admin_user)
      aggregate_failures do
        expect do
          delete user_path(user)
        end.to change(User, :count).by(-1)
        expect(response).to redirect_to users_url
      end
    end
  end

end