RailsチュートリアルのテストをRspecで書いてみた[第13章]
はじめに
今回は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
第13章
リスト13.7: Micropostモデルのバリデーションに対するテスト
ここでMicropostモデルのテストデータをFactoryBotを使用して作成します。
spec/factories/microposts.rb FactoryBot.define do factory :micropost do content { "micropost test" } association :user end end
association :userとすることで、関連付けされてあるUserオブジェクトを自動で生成してくれます!!
modelスペックは以下の通りです。
spec/models/micropost_spec.rb require 'rails_helper' RSpec.describe Micropost, type: :model do # associationで関連付けしているので、Userオブジェクトを自分で作らなくていい!! let(:micropost) { FactoryBot.create(:micropost) } it "is valid with micropost's test data" do expect(micropost).to be_valid end it "is invalid with no user_id" do micropost.user_id = nil expect(micropost).to be_invalid end it "is invalid with no content" do micropost.content = " " expect(micropost).to be_invalid end it "is invalid with 141-letter mails" do micropost.content = "a" * 141 expect(micropost).to be_invalid end end
ちなみに、associationを使わないと以下のように冗長なものになります。
let(:user) { FactoryBot.create(:user) } let(:micropost) { FactoryBot.create(:micropost, user_id: user.id) }
リスト13.14: Micropostモデルの順序付けをテストする
まずはmicropostのテストデータを追加します。
spec/factories/microposts.rb FactoryBot.define do factory :micropost do content { "micropost test" } created_at { 10.minutes.ago } association :user trait :yesterday do content { "yesterday" } created_at { 1.day.ago } end trait :day_before_yesterday do content { "day_before_yesterday" } created_at { 2.days.ago } end trait :now do content { "now!" } created_at { Time.zone.now } end end end
modelスペックは以下の通りです。
spec/models/micropost_spec.rb require 'rails_helper' RSpec.describe Micropost, type: :model do ・・・ describe "Sort by latest" do # 評価される前にdbに保存されていないといけないので、「let!」を使用します。 let!(:day_before_yesterday) { FactoryBot.create(:micropost, :day_before_yesterday) } # FactoryBot.create(:micropost, :now) を一番上に持ってくるとテストの意味がなくなるので注意です。 let!(:now) { FactoryBot.create(:micropost, :now) } let!(:yesterday) { FactoryBot.create(:micropost, :yesterday) } it "succeeds" do expect(Micropost.first).to eq now end end end
リスト13.20: dependent: :destroyのテスト
spec/models/user_spec.rb require 'rails_helper' RSpec.describe User, type: :model do let(:user) { FactoryBot.build(:user) } ・・・ describe "dependent: :destroy" do before do user.save user.microposts.create!(content: "Lorem ipsum") end it "succeeds" do expect do user.destroy end.to change(Micropost, :count).by(-1) end end end
リスト13.31: Micropostsコントローラの認可テスト
spec/requests/micropost_request_spec.rb require 'rails_helper' RSpec.describe "Microposts", type: :request do describe "Microposts#create" do let(:micropost) { FactoryBot.attributes_for(:micropost) } let(:post_request) { post microposts_path, params: { micropost: micropost } } context "when not logged in" do it "doesn't change Micropost's count" do expect { post_request }.to change(Micropost, :count).by(0) end it "redirects to login_url" do expect(post_request).to redirect_to login_url end end end describe "Microposts#destroy" do let!(:micropost) { FactoryBot.create(:micropost) } let(:delete_request) { delete micropost_path(micropost) } context "when not logged in" do it "doesn't change Micropost's count" do expect { delete_request }.to change(Micropost, :count).by(0) end it "redirects to login_url" do expect(delete_request).to redirect_to login_url end end end end
リスト13.55: 間違ったユーザによるマイクロポスト削除に対してテストする
spec/requests/micropost_request_spec.rb require 'rails_helper' RSpec.describe "Microposts", type: :request do ・・・ describe "Microposts#destroy" do let!(:micropost) { FactoryBot.create(:micropost) } let(:delete_request) { delete micropost_path(micropost) } context "when not logged in" do it "doesn't change Micropost's count" do expect { delete_request }.to change(Micropost, :count).by(0) end it "redirects to login_url" do expect(delete_request).to redirect_to login_url end end #ここからが今回のテストです。 context "when logged in user tyies to delete another user's micropost" do let(:user) { FactoryBot.create(:user) } before { log_in_as(user) } it "doesn't change Micropost's count" do expect { delete_request }.to change(Micropost, :count).by(0) end it "redirects to root_url" do expect(delete_request).to redirect_to root_url end end end end
リスト13.56: マイクロポストのUIに対するテスト
systemスペックで書いています。
spec/system/micropost_interface_spec.rb require 'rails_helper' RSpec.describe "MicropostsInterfaces", type: :system do let(:user) { FactoryBot.create(:user) } let(:micropost) { FactoryBot.create(:micropost) } before do 34.times do content = Faker::Lorem.sentence(word_count: 5) user.microposts.create!(content: content) end end scenario "micropost interface" do login_as(user) click_on "Home" # 無効な送信 click_on "Post" expect(has_css?('.alert-danger')).to be_truthy # 正しいページネーションリンク click_on "2" expect(URI.parse(current_url).query).to eq "page=2" # 有効な送信 valid_content = "This micropost really ties the room together" fill_in "micropost_content", with: valid_content expect do click_on "Post" expect(current_path).to eq root_path expect(has_css?('.alert-success')).to be_truthy end.to change(Micropost, :count).by(1) # 投稿を削除する expect do page.accept_confirm do all('ol li')[0].click_on "delete" end expect(current_path).to eq root_path expect(has_css?('.alert-success')).to be_truthy end.to change(Micropost, :count).by(-1) # 違うユーザのプロフィールにアクセス(削除リンクがないことを確認) visit user_path(micropost.user) expect(page).not_to have_link "delete" end end
ページネーションのテストがある為、予め34個のマイクロポストを作成しています。
before do 34.times do content = Faker::Lorem.sentence(word_count: 5) user.microposts.create!(content: content) end end
login_as(user)は自作のヘルパーメソッドです。
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 SystemHelper end
「正しいページネーションリンク」では以下を参考にしました。
capybaraでcurrent_pathが一致しているかどうか調べるのにハマった - Qiita
# current_path => クエリを取得しないので注意!! URI.parse(current_url).query
「投稿を削除する」では以下を参考にしました。
【Rails】Selenium/RSpecでconfirmダイアログのテストをする - Qiita
使えるRSpec入門・その4「どんなブラウザ操作も自由自在!逆引きCapybara大辞典」 - Qiita
all('ol li')[0].click_on "delete" # allメソッドで条件(ol要素内のli要素)に合致した要素の配列が返ってくる。
また、マイクロポストの数が減っているかを確認するテストですが、以下のようにするとテストは通りません。
expect do page.accept_confirm do all('ol li')[0].click_on "delete" end end.to change(Micropost, :count).by(-1) #=>expected `Micropost.count` to have changed by -1, but was changed by 0
これは、Ajaxの処理が完了する前にMicropost.countをチェックしているからです。
これを回避する為には一つ以上の動作を挟んであげる必要があります。
リスト13.64: 画像アップロードをテストするためのテンプレート
spec/system/micropost_interface_spec.rb require 'rails_helper' RSpec.describe "MicropostsInterfaces", type: :system do let(:user) { FactoryBot.create(:user) } let(:micropost) { FactoryBot.create(:micropost) } before do 34.times do content = Faker::Lorem.sentence(word_count: 5) user.microposts.create!(content: content) end end scenario "micropost interface" do login_as(user) click_on "Home" # 無効な送信 click_on "Post" expect(has_css?('.alert-danger')).to be_truthy # 正しいページネーションリンク click_on "2" expect(URI.parse(current_url).query).to eq "page=2" # 有効な送信 valid_content = "This micropost really ties the room together" fill_in "micropost_content", with: valid_content # 下の一文追加 attach_file 'micropost[image]', "#{Rails.root}/spec/fixtures/kitten.jpg" expect do click_on "Post" expect(current_path).to eq root_path expect(has_css?('.alert-success')).to be_truthy # 下の一文追加 expect(page).to have_selector "img[src$='kitten.jpg']" end.to change(Micropost, :count).by(1) expect do page.accept_confirm do all('ol li')[0].click_on "delete" end expect(current_path).to eq root_path expect(has_css?('.alert-success')).to be_truthy end.to change(Micropost, :count).by(-1) # 違うユーザのプロフィールにアクセス(削除リンクがないことを確認) visit user_path(micropost.user) expect(page).not_to have_link "delete" end end
画像追加の部分はattach_fileを使用しています。
attach_file 'micropost[image]', "#{Rails.root}/spec/fixtures/kitten.jpg" # 第2引数のpathは適宜変更してください。
画面に反映されたかを検査する際にCSSのセレクタである$=を使用しています。
img[src$='kitten.jpg'] /* src属性が'kitten.jpg'で終わるimg要素という意味です。*/