当ブログではPRや広告を掲載しています

【Rails】RSpec いいね機能のテストの組み方(capybara)

プログラミング
この記事は…
  • railsで実装済みのいいね機能をRSpecでテスト
  • RSpec、Capybaraでのテストの書き方を解説
  • rails初心者〜中級者向け
スポンサーリンク

前提

この記事では、railsで実装した「いいね機能」についてRSpecテストをどのように実装するか、を解説していきます。

このため、以下のことを前提として解説を進めます。

いいね機能が実装済みであること

テスト対象の機能が無いと、そもそもテストしても上手くいっているか分かりませんよね。

いいね機能の実装方法は以下の記事で解説しているので、参考にしてください。

機能の実装方法はいくつもありますが、テストとしてやることは基本的に同じなので、使える部分は使っていただく感じがいいと思います。

RSpec導入済みであること

自動テストをやる際に多くの開発者が使う有名なgemなので、解説は不要かなと思います。

この記事では以下のgemを使っていきます。

  • rspec-rails
  • capybara

RSpec テスト

今回、RSpecで行うテストは大きく分けて2つです。

モデル側でのアソシエーションやバリデーションのテスト、Capybara を用いた画面側の表示確認のテストです。
順番に解説していきましょう。

Factory を定義

テストデータ作成のために、Factoryを用いたダミーデータを定義します。

favoriteモデルはアソシエーションを組んでいる関係から、以下の記述のみでOKです。

spec/factories/favorites.rb
FactoryBot.define do
  factory :favorite do
    user
    book
  end
end

アソシエーションでUserとBookを参照しているので、それらのコードも載せておきます。
※カラムは自分の環境に合うように読み替えてください。

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name { Faker::Lorem.characters(number: 10) }
    email { Faker::Internet.email }
    password { 'password' }
    password_confirmation { 'password' }
  end
end
spec/factories/books.rb
FactoryBot.define do
  factory :book do
    title { Faker::Lorem.characters(number: 5) }
    user
  end
end

モデルのテスト

モデル側のテストは、バリデーションとアソシエーションの確認のみのため、いたってシンプルです。

まず先にテストコードを紹介しておきます。

spec/models/favorite_spec.rb
require 'rails_helper'

RSpec.describe 'Favoriteモデルのテスト', type: :model do
  describe 'バリデーションのテスト' do
    subject { favorite.valid? }

    let!(:other_favorite) { create(:favorite) }
    let(:favorite) { build(:favorite) }

    context '1User 1Book 1いいね' do
      it 'あるUserが同じBookにいいね出来ないこと' do
        favorite.user = other_favorite.user
        favorite.book = other_favorite.book
        is_expected.to eq false
      end
    end
  end

  describe 'アソシエーションのテスト' do
    context 'Userモデルとの関係' do
      it 'N:1となっている' do
        expect(Favorite.reflect_on_association(:user).macro).to eq :belongs_to
      end
    end

    context 'Bookモデルとの関係' do
      it 'N:1となっている' do
        expect(Favorite.reflect_on_association(:book).macro).to eq :belongs_to
      end
    end
  end
end

最初の let の部分で、Factoryで定義したfavoriteを使っており、2つのfavoriteレコードを作成しています。
2つ作る理由は、バリデーションで重複レコードをテストするためです。

バリデーションのテストは少し複雑で、1ユーザーが同じBookに何回も”いいね”出来ないように制御しているか、のテストです。

favoriteモデルでは以下のようにバリデーションを組んでいるので、同じBookに複数回いいねすることが出来ないようになっています。

app/models/favorite.rb
class Favorite < ApplicationRecord
  belongs_to :user
  belongs_to :book
  validates :book_id, uniqueness: {scope: :user_id}
end

Capybara でのテスト

今回テスト対象となる”いいね”のリンクは、以下のようなパーシャルビューとなっています。

app/views/favorites/_thumbs.rb
<%= link_to book_favorite_path(book), method: :post, remote:true, id: 'thumbs-up' do %>
  <i class="far fa-thumbs-up"></i>
<% end %>
<%= book.favorite.size %>

Capybara でJSは無理

いいね機能はだいたい非同期通信でやり取りするような実装になっているので、JSが絡むと単純にCapybaraだけではテスト出来ないという問題があります。

なので、以下のようなテストコードを実行しても画面が変化しないので、意図した結果が得られません。

find_all('i.far')[0].click

ではどうすればいいかと言うと、seleniumを使うことがCapybara公式チームより推奨されています。

selenium 入れたくない

selenium は、ざっくり言うとブラウザを立ち上げてプログラムがクリック操作などを実際に行うようにするためのgemです。

まあユーザー操作をプログラムが代わりにやってくれるヤツということです。

しかし、“いいね”ボタンを押すためだけに色々追加するのはメンドウだし、コスパも悪いので、ちょっと工夫しましょう。

工夫してseleniumを回避

どうにかCapybaraだけでテストできないものか考え、思いついたのが先に”いいね済”にしてしまう方法でした。

未いいね”状態のテストもしたいので、Bookレコードを2つ作成し、1つだけ”いいね済”にすれば、両方のテストができます。

というわけで、出来上がったテストがこちらです。

spec/system/capybara_spec.rb
require 'rails_helper'

describe 'book一覧画面のテスト' do
  let(:user) { create(:user) }
  let!(:other_user) { create(:user) }
  let!(:book) { create(:book, user: user) }
  let!(:other_book) { create(:book, user: other_user) }
  let!(:favorite) { create(:favorite, book: book, user: user) } # 1ついいね済にしておく

  before do  
    visit books_path
  end

  context 'いいね確認' do
    it 'リンクが諸々正しい' do
      expect(page).to have_link '', href: book_favorite_path(book) #リンクが正しい
      expect(page).to have_css('i.far') #いいねの表示
      expect(page).to have_css('i.fas') #いいね済の表示
    end
  end
end

最後にrspecのコマンドを実行して結果を確認してみましょう!

bundle exec rspec spec/system --format documentation

まとめ

JSが絡むテストはクセがあることがわかりました。

seleniumを入れれば将来的にもテストが楽になるかもしれませんね。(開発者もユーザーテストするからカバー出来るよねって話もありますが笑)

とはいえ、今回のような方法で、JSを動かさずとも表示内容のテストを組めるので、いろんな所で応用して使っていきたいものですね。

コメント