見出し画像

RSpecのsystemテストでドロップダウンリストを選択させるテストを記述

自作アプリについて、自分で実装したいと思った機能はおおかた実装しましたので、テストコードを書きました。

テストを書くにあたって、やや苦労した部分があったので備忘録がてら書いておきます。


テストしたいこと

「ほしいものリスト」から2つのitemsを選択し、2つのitem_idをとってComparisonsテーブルに保存する。このとき、以下の条件を満たすことを確認したい。

  • 存在する2つのアイテムを比較できる

  • 片方のアイテムが空だと比較できない

  • 両方同じアイテムだと比較できない

  • 過去に作成した比較と同一の場合は、新規作成されずに既存の比較ページに飛ぶ(この挙動自体はコントローラとモデルで記述済み)

  • 自分で作った比較は削除できる

  • 自分以外のユーザーの比較は表示されず、削除もできない

書いたコード

require 'rails_helper'

RSpec.describe "Comparisons", type: :system do
  before do
    # ユーザーを1人作成
    @user = FactoryBot.create(:user)
    # そのユーザーのアイテムを2個作成
    @item1 = FactoryBot.create(:item, user_id: @user.id, name: 'アイテム1')
    @item2 = FactoryBot.create(:item, user_id: @user.id, name: 'アイテム2')
  end

  describe '比較機能のテスト' do
    context '正しく登録できるケース' do
      it 'primary_item_id, secondary_item_idがあれば登録できる' do
        # ログインする
        visit new_user_session_path
        fill_in 'Email', with: @user.email
        fill_in 'Password', with: @user.password
        find('input[name="commit"]').click
        sleep 1
        expect(current_path).to eq items_path
        # 比較新規登録画面へのボタンがある
        expect(page).to have_content('比較する')
        expect(
          all('.nav-item')[2].click
        ).to have_content('比較を作成')
        # 比較新規登録画面に遷移する
        visit new_comparison_path
        # primary_item_id, secondary_item_idをドロップダウンから選択する
        within('form') do
          select @item1.name, from: '1つめのアイテム'
          select @item2.name, from: '2つめのアイテム'
        end
        expect {
          click_button '作成'
          sleep 1
        }.to change { Comparison.count }.by(1)
      end
    end

    context '登録できないケース' do
      it 'primary_item_idが空だと登録できない' do
        # ログインする
        visit new_user_session_path
        fill_in 'Email', with: @user.email
        fill_in 'Password', with: @user.password
        find('input[name="commit"]').click
        sleep 1
        expect(current_path).to eq items_path
        # 比較新規登録画面へのボタンがある
        expect(page).to have_content('比較する')
        expect(
          all('.nav-item')[2].click
        ).to have_content('比較を作成')
        # 比較新規登録画面に遷移する
        visit new_comparison_path
        # secondary_item_idをドロップダウンから選択する
        within('form') do
          select @item2.name, from: '2つめのアイテム'
        end
        expect {
          click_button '作成'
          sleep 1
        }.to change { Comparison.count }.by(0)
      end

      it 'secondary_item_idが空だと登録できない' do
        # ログインする
        visit new_user_session_path
        fill_in 'Email', with: @user.email
        fill_in 'Password', with: @user.password
        find('input[name="commit"]').click
        sleep 1
        expect(current_path).to eq items_path
        # 比較新規登録画面へのボタンがある
        expect(page).to have_content('比較する')
        expect(
          all('.nav-item')[2].click
        ).to have_content('比較を作成')
        # 比較新規登録画面に遷移する
        visit new_comparison_path
        # primary_item_idをドロップダウンから選択する
        within('form') do
          select @item1.name, from: '1つめのアイテム'
        end
        expect {
          click_button '作成'
          sleep 1
        }.to change { Comparison.count }.by(0)
      end

      it 'primary_item_idとsecondary_item_idが同じだと登録できない' do
        # ログインする
        visit new_user_session_path
        fill_in 'Email', with: @user.email
        fill_in 'Password', with: @user.password
        find('input[name="commit"]').click
        sleep 1
        expect(current_path).to eq items_path
        # 比較新規登録画面へのボタンがある
        expect(page).to have_content('比較する')
        expect(
          all('.nav-item')[2].click
        ).to have_content('比較を作成')
        # 比較新規登録画面に遷移する
        visit new_comparison_path
        # primary_item_id, secondary_item_idをドロップダウンから選択する
        within('form') do
          select @item1.name, from: '1つめのアイテム'
          select @item1.name, from: '2つめのアイテム'
        end
        expect {
          click_button '作成'
          sleep 1
        }.to change { Comparison.count }.by(0)
      end

      it '同じ組み合わせの比較は新しく作成されない' do
        # ログインする
        visit new_user_session_path
        fill_in 'Email', with: @user.email
        fill_in 'Password', with: @user.password
        find('input[name="commit"]').click
        sleep 1
        expect(current_path).to eq items_path
        # 比較新規登録画面へのボタンがある
        expect(page).to have_content('比較する')
        expect(
          all('.nav-item')[2].click
        ).to have_content('比較を作成')
        # 比較新規登録画面に遷移する
        visit new_comparison_path
        # primary_item_id, secondary_item_idをドロップダウンから選択する
        within('form') do
          select @item1.name, from: '1つめのアイテム'
          select @item2.name, from: '2つめのアイテム'
        end
        expect {
          click_button '作成'
          sleep 1
        }.to change { Comparison.count }.by(1)
        # 比較新規登録画面に遷移する
        visit new_comparison_path
        # primary_item_id, secondary_item_idをドロップダウンから選択する
        within('form') do
          select @item1.name, from: '1つめのアイテム'
          select @item2.name, from: '2つめのアイテム'
        end
        expect {
          click_button '作成'
          sleep 1
        }.to change { Comparison.count }.by(0)
      end
    end

    context '比較削除' do
      it '比較を削除できる' do
        # 比較を作成
        @comparison = Comparison.create(primary_item_id: @item1.id, secondary_item_id: @item2.id, user_id: @user.id)
        # ログインする
        visit new_user_session_path
        fill_in 'Email', with: @user.email
        fill_in 'Password', with: @user.password
        find('input[name="commit"]').click
        sleep 1
        # 比較一覧画面に遷移する
        visit comparisons_path
        # 比較削除ボタンがある
        expect(page).to have_content('削除')
        expect {
          page.accept_confirm do
            find_link('削除', href: comparison_path(@comparison.id)).click
          end
          sleep 1
        }.to change { Comparison.count }.by(-1)
      end

      it '自分以外のユーザーの比較詳細は閲覧できず、削除もできない' do
        @comparison = Comparison.create(primary_item_id: @item1.id, secondary_item_id: @item2.id, user_id: @user.id)
        # 別のユーザーを作成
        @user2 = FactoryBot.create(:user)
        # ログインする
        visit new_user_session_path
        fill_in 'Email', with: @user2.email
        fill_in 'Password', with: @user2.password
        find('input[name="commit"]').click
        sleep 1
        expect(current_path).to eq items_path
        # 比較一覧画面に遷移する
        visit comparisons_path
        # 比較削除ボタンがない
        expect(page).to have_no_content('削除')
        # URLを直接入力しても遷移できない
        visit comparison_path(@comparison.id)
        expect(current_path).to eq comparisons_path
      end
    end
  end
end

結果

苦労したところ

RSpec自体は好きなんですが、どうも今だにインプット・ボタン系の要素の操作をどのように指示するか、ちゃんと把握しきれていません。

具体的には、今回めちゃくちゃ詰まったのは、最初の「アイテム1と2をドロップダウンから選ぶ」ところの選択と、最後の「削除ボタンを押す」という動作。

悪戦苦闘の末、ドロップダウン選択はこれでいけました。

within('form') do
  select @item1.name, from: '1つめのアイテム'
  select @item2.name, from: '2つめのアイテム'
end

削除ボタンは下のような感じ。
わかってしまえば「そりゃそう」なんだけど、ゼロベースでなかなか書けなかったなぁ。

expect {
  page.accept_confirm do
    find_link('削除', href: comparison_path(@comparison.id)).click
  end
  sleep 1
}.to change { Comparison.count }.by(-1)

上記のコードを日本語に直すと、

「私が期待することは以下です。
これから出てくる確認メッセージは”はい”と答えてください。
「削除」という文字のある、商品比較ページにリンクするボタンを押して、1秒待ってください。
そしたら、比較の件数が1減っているはずです。」

テストコードの声

テストコードはできるだけがっちり書こう

実は上記のテストコード作成の途中で気づいたのですが、「他人が作った比較ページにURL直打ちで飛べちゃう」という設定ミスがありました。本番サービスだったら大事故です。
テストコードのおかげで発覚しました。

テストコードは、「このアプリの機能ってこうだよな」「この動きはされちゃあ困るよな」という要望を、相当の再現性を伴いつつ担保してくれる素敵な仕組みだと思います。

なにより、個人開発の場合は今回のように考慮漏れの部分に気づくチャンスでもあるので、テストコードはしっかりと書くのが良いでしょう。

で、上のコードのように絶望的に長いコードに見えるんですが、ぶっちゃけ「なぜ、何をテストするのか」という意図がわかっていれば、細かい記述は今後どんどんAI任せになったりするでしょう。
そうなれば、人間はもうすこし抽象的に「こんなテストやってくれよ」って考える役割に集中できるので、テストコードの記述については「記述方法の暗記」よりも「テスト自体の設計の練習」を意識するのが良いと感じました。

今日は以上です。最後までお読みいただきありがとうございました。

この記事が気に入ったらサポートをしてみませんか?