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

はじめに

今回は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

第14章

リスト14.4: Relationshipモデルのバリデーションをテストする

spec/models/relationships_spec.rb

require 'rails_helper'

RSpec.describe Relationship, type: :model do
  describe "validation" do
    let(:user) { FactoryBot.create(:user) }
    let(:other_user) { FactoryBot.create(:user) }
    let(:relationship) { user.active_relationships.build(followed_id: other_user.id) }

    it "is valid with test data" do
      expect(relationship).to be_valid
    end

    describe "presence" do
      it "is invalid without follower_id" do
        relationship.follower_id = nil
        expect(relationship).to be_invalid
      end
      it "is invalid without followed_id" do
        relationship.followed_id = nil
        expect(relationship).to be_invalid
      end
    end
  end
end

リスト14.9: Relationshipモデルのバリデーションをテストする

spec/models/user_spec.rb

require 'rails_helper'

RSpec.describe User, type: :model do
・・・
  describe "follow and unfollow" do
    let(:user) { FactoryBot.create(:user) }
    let(:other_user) { FactoryBot.create(:user) }

    before { user.follow(other_user) }

    describe "follow" do
      it "succeeds" do
        expect(user.following?(other_user)).to be_truthy
      end
    end

    describe "unfollow" do
      it "succeeds" do
        user.unfollow(other_user)
        expect(user.following?(other_user)).to be_falsy
      end
    end
  end
end

リスト14.13: followersに対するテスト

spec/models/user_spec.rb

require 'rails_helper'

RSpec.describe User, type: :model do
・・・
  describe "follow and unfollow" do
    let(:user) { FactoryBot.create(:user) }
    let(:other_user) { FactoryBot.create(:user) }

    before { user.follow(other_user) }

    describe "follow" do
      it "succeeds" do
        expect(user.following?(other_user)).to be_truthy
      end
   # ここから
      describe "followers" do
        it "succeeds" do
          expect(other_user.followers.include?(user)).to be_truthy
        end
      end
   # ここまで
    end
   
    describe "unfollow" do
      it "succeeds" do
        user.unfollow(other_user)
        expect(user.following?(other_user)).to be_falsy
      end
    end
  end
end

リスト14.24: フォロー/フォロワーページの認可をテストする

spec/models/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 following when not logged in' do
      get following_user_path(user)
      expect(response).to redirect_to login_url
    end

    it 'redirects followers when not logged in' do
      get followers_user_path(user)
      expect(response).to redirect_to login_url
    end
  end
end

リスト14.29: following/followerページのテスト

spec/system/followings_spec.rb

require 'rails_helper'

RSpec.describe "Followings", type: :system do
  let(:user) { FactoryBot.create(:user) }
  let(:other_users) { FactoryBot.create_list(:user, 20) }

  before do
    other_users[0..9].each do |other_user|
      user.active_relationships.create!(followed_id: other_user.id)
      user.passive_relationships.create!(follower_id: other_user.id)
    end
    login_as(user)
  end

  scenario "The number of following and followers is correct" do
    click_on "following"
    expect(user.following.count).to eq 10
    user.following.each do |u|
      expect(page).to have_link u.name, href: user_path(u)
    end

    click_on "followers"
    expect(user.followers.count).to eq 10
    user.followers.each do |u|
      expect(page).to have_link u.name, href: user_path(u)
    end
  end
end

create_listメソッドを使用すると、第2引数で指定した数のインスタンスをまとめて生成してくれます。

FactoryBot.create_list(:user, 20)
# =>20個のテストデータを作成してくれます

リスト14.31: リレーションシップの基本的なアクセス制御に対するテスト

spec/requests/relationships_request_spec.rb

require 'rails_helper'

RSpec.describe "Relationships", type: :request do

  describe "Relationships#create" do
    let(:post_request) { post relationships_path }

    context "when not logged in" do
      it "doesn't change Relationship's count" do
        expect { post_request }.to change(Relationship, :count).by(0)
      end

      it "redirects to login_url" do
        expect(post_request).to redirect_to login_url
      end
    end
  end

  describe "Relationships#destroy" do
    let(:user) { FactoryBot.create(:user) }
    let(:other_user) { FactoryBot.create(:user) }
    let(:delete_request) { delete relationship_path(other_user) }

    before { user.following << other_user }

    context "when not logged in" do
      it "doesn't change Relationship's count" do
        expect { delete_request }.to change(Relationship, :count).by(0)
      end

      it "redirects to login_url" do
        expect(delete_request).to redirect_to login_url
      end
    end
  end
end

リスト14.40: [Follow] / [Unfollow]ボタンをテストする

spec/system/followings_spec.rb

require 'rails_helper'

RSpec.describe "Followings", type: :system do
  let(:user) { FactoryBot.create(:user) }
  let(:other_users) { FactoryBot.create_list(:user, 20) }

  before do
    other_users[0..9].each do |other_user|
      user.active_relationships.create!(followed_id: other_user.id)
      user.passive_relationships.create!(follower_id: other_user.id)
    end
    login_as(user)
  end
・・・
  scenario "When user clicks on Unfollow, the number of following increases by -1" do
    visit user_path(other_users.first.id)
    expect do
      click_on "Unfollow"
      expect(page).not_to have_link "Unfollow"
      # Ajaxの処理待ちの為に入れています
      visit current_path
    end.to change(user.following, :count).by(-1)
  end

  scenario "When user clicks on Follow, the number of following increases by 1" do
    visit user_path(other_users.last.id)
    expect do
      click_on "Follow"
      expect(page).not_to have_link "Follow"
      # Ajaxの処理待ちの為に入れています
      visit current_path
    end.to change(user.following, :count).by(1)
  end
end

リスト14.42: ステータスフィールドのテスト

ユーザのテストデータを作成した際、マイクロポストも一緒に作成されるようにFactoryBotの機能の一つコールバックを使用します。

spec/factories/users.rb

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

    trait :admin do
      admin { true }
    end

    trait :no_activated do
      activated { false }
      activated_at { nil }
    end

# 新しく追加しています。
    trait :with_microposts do
      after(:create) { |user| create_list(:micropost, 5, user: user)}
    end
  end
end
spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
・・・
  describe "def feed" do
    let(:user) { FactoryBot.create(:user, :with_microposts) }
    let(:other_user) { FactoryBot.create(:user, :with_microposts) }

    context "when user is following other_user" do

      before { user.active_relationships.create!(followed_id: other_user.id) }

      it "contains other user's microposts within the user's Micropost" do
        other_user.microposts.each do |post_following|
          expect(user.feed.include?(post_following)).to be_truthy
        end
      end

      it "contains the user's own microposts in the user's Micropost" do
        user.microposts.each do |post_self|
          expect(user.feed.include?(post_self)).to be_truthy
        end
      end
    end

    context "when user is not following other_user" do
      it "doesn't contain other user's microposts within the user's Micropost" do
        other_user.microposts.each do |post_unfollowed|
          expect(user.feed.include?(post_unfollowed)).to be_falsy
        end
      end
    end
  end
end