第6回 Ruby on Rails〜タスク管理アプリケーション テストの実装④(System Specの実装part3)〜

Ruby

はじめに

今回もRuby on Railsの自動テストについて学んでいきます。
前回に引き続きタスク管理アプリにシステムテストを実装していきます。
尚、自動テストについては今回で最後になります。

shared_examplesの利用

現在のtasks_spec.rbのコードを見ると、一覧表示機能のユーザーAがログインしている時のcontext内のitと、詳細表示機能のユーザーAがログインしている時のcontext内のitのコードは同じでどちらも次のようになっています。

spec/system/tasks_spec.rb

it 'ユーザーAが作成したタスクが表示される' do
 expect(page).to have_content '最初のタスク'
end

よりDRYなコードにするためにこの部分を共通化します。

Rspecではitを共通化する方法としてshared_examplesという仕組みを用意しています。
exampleはitなどの期待する挙動を示す部分のことです。このexampleをいくつかまとめて名前をつけ、テストケース間でシェアできます。

ここでは重複しているitを含むshared_examplesを作ります。shared_examplesの名前は「ユーザーAが作成したタスクが表示される」とします。

shared_examples_for 'ユーザーAが作成したタスクが表示される' do
 it { expect(page).to have_content '最初のタスク' }
end

次にここで共通化したitの処理が書かれていた箇所を、このshared_examplesを利用するという次の処理に置き換えます。

it_behaves_like 'ユーザーAが作成したタスクが表示される'

これらの変更を加えたtasks_spec.rbは以下の通りです。

spec/system/tasks_spec.rb

require 'rails_helper'

describe 'タスク管理機能', type: :system do
  let(:user_a) { FactoryBot.create(:user, name: 'ユーザーA', email: 'a@example.com') }
  let(:user_b) { FactoryBot.create(:user, name: 'ユーザーB', email: 'b@example.com') }
  let!(:task_a) { FactoryBot.create(:task, name: '最初のタスク', user: user_a) }

  before do
    FactoryBot.create(:task, name: '最初のタスク', user: user_a)
    visit login_path
    fill_in 'メールアドレス', with: login_user.email
    fill_in 'パスワード', with: login_user.password
    click_button 'ログインする'
  end

  shared_examples_for 'ユーザーAが作成したタスクが表示される' do
    it { expect(page).to have_content '最初のタスク' }
  end

  describe '一覧表示機能' do
    context 'ユーザーAがログインしている時' do
      let(:login_user) { user_a }

      it_behaves_like 'ユーザーAが作成したタスクが表示される'
    end

    context 'ユーザーBがログインしているとき' do
      let(:login_user) { user_b }

      it 'ユーザーAが作成したタスクが表示されない' do
        expect(page).to have_no_content '最初のタスク'
      end
    end
  end

  describe '詳細表示機能' do
    context 'ユーザーAがログインしている時' do
      let(:login_user) { user_a }

      before do
        visit task_path(task_a)
      end

      it_behaves_like 'ユーザーAが作成したタスクが表示される'
    end
  end
end

テストを実行すると無事成功します。

$ bundle exec rspec spec/system/tasks_spec.rb
...

Finished in 11.48 seconds (files took 10.89 seconds to load)
3 examples, 0 failures

これでitの処理を共通化できました。

新規作成機能のSystem Spec

タスクの新規作成機能のSystem Specを記述します。
タスク管理アプリにはタスクの名称を入力しないと登録できないよう検証機能を追加済みなので、タスク名称を入力すると登録ができること、タスク名称を入力しないと検証エラーになることの2つのテストケースを記述します。

tasks_spec.rbを以下のように編集します。

spec/system/tasks_spec.rb

require 'rails_helper'

describe 'タスク管理機能', type: :system do
  let(:user_a) { FactoryBot.create(:user, name: 'ユーザーA', email: 'a@example.com') }
  let(:user_b) { FactoryBot.create(:user, name: 'ユーザーB', email: 'b@example.com') }
  let!(:task_a) { FactoryBot.create(:task, name: '最初のタスク', user: user_a) }

  before do
    FactoryBot.create(:task, name: '最初のタスク', user: user_a)
    visit login_path
    fill_in 'メールアドレス', with: login_user.email
    fill_in 'パスワード', with: login_user.password
    click_button 'ログインする'
  end

  shared_examples_for 'ユーザーAが作成したタスクが表示される' do
    it { expect(page).to have_content '最初のタスク' }
  end

  describe '一覧表示機能' do
    context 'ユーザーAがログインしている時' do
      let(:login_user) { user_a }

      it_behaves_like 'ユーザーAが作成したタスクが表示される'
    end

    context 'ユーザーBがログインしているとき' do
      let(:login_user) { user_b }

      it 'ユーザーAが作成したタスクが表示されない' do
        expect(page).to have_no_content '最初のタスク'
      end
    end
  end

  describe '詳細表示機能' do
    context 'ユーザーAがログインしている時' do
      let(:login_user) { user_a }

      before do
        visit task_path(task_a)
      end

      it_behaves_like 'ユーザーAが作成したタスクが表示される'
    end
  end

  describe '新規作成機能' do
    let(:login_user) { user_a }

    before do
      visit new_task_path
      fill_in '名称', with: task_name // ①
      click_button '登録する'
    end

    context '新規作成画面で名称を入力したとき' do
      let(:task_name) { '新規作成のテストを書く' }

      it '正常に登録される' do
        expect(page).to have_selector '.alert-success', text: '新規作成のテストを書く' // ②
      end
    end

    context '新規作成画面で名称を入力しなかったとき' do
      let(:task_name) { '' }

      it 'エラーとなる' do
        within '#error_explanation' do // ③
          expect(page).to have_content '名称 を入力してください'
        end
      end
    end
  end
end

コードの中身を見ると、①の箇所ではtask_nameというletを利用して続くcontextで名称を入力するかしないかの違いを吸収します。

②では、have_selectorというマッチャを使って、alert-successというCSSクラスのついた、テキストに「新規作成のテストを書く」を含む要素があるか検査しています。

③ではwithinを用いて、error_explanationというidの要素内に探索する範囲を狭めて、「名称 を入力してください」というエラーメッセージが含まれているかどうかを調べています。

テストを実行すると無事成功します。

$ bundle exec rspec spec/system/tasks_spec.rb
.....

Finished in 7.54 seconds (files took 2.53 seconds to load)
5 examples, 0 failures

新規作成機能のSystemSpecを作成できました。

letの上書き

これまでで、letは利用する箇所の外側でも、内側でも定義できるということを説明しましたが、もう一つ重要な点が処理経路上に複数回、同じ名前のletを定義できるということです。

require 'rails_helper'

describe 'タスク管理機能', type: :system do
  let(:user_a) { FactoryBot.create(:user, name: 'ユーザーA', email: 'a@example.com') }
  let(:user_b) { FactoryBot.create(:user, name: 'ユーザーB', email: 'b@example.com') }
  let!(:task_a) { FactoryBot.create(:task, name: '最初のタスク', user: user_a) }

  before do
    FactoryBot.create(:task, name: '最初のタスク', user: user_a)
    visit login_path
    fill_in 'メールアドレス', with: login_user.email
    fill_in 'パスワード', with: login_user.password
    click_button 'ログインする'
  end

  shared_examples_for 'ユーザーAが作成したタスクが表示される' do
    it { expect(page).to have_content '最初のタスク' }
  end

 ・・・

  describe '新規作成機能' do
    let(:login_user) { user_a }
    let(:task_name) { '新規作成のテストを書く' } // デフォルトの設定

    before do
      visit new_task_path
      fill_in '名称', with: task_name
      click_button '登録する'
    end

    context '新規作成画面で名称を入力したとき' do
      it '正常に登録される' do
        expect(page).to have_selector '.alert-success', text: '新規作成のテストを書く'
      end
    end

    context '新規作成画面で名称を入力しなかったとき' do
      let(:task_name) { '' } // 上書き

      it 'エラーとなる' do
        within '#error_explanation' do
          expect(page).to have_content '名称 を入力してください'
        end
      end
    end
  end
end

処理経路上に複数回、同じ名前のletを定義したときは、常に下の階層に定義したletが使われます。つまりletは上書きできます。

上記のコードでは、新規作成機能で利用するtask_nameをデフォルトでは「新規作成のテストを書く」にしておき、名称を入力しないテストケースでは値をから文字列に上書きしています。

このSpecを実行すると全て成功します。

$ bundle exec rspec spec/system/tasks_spec.rb
.....

Finished in 10.13 seconds (files took 3.07 seconds to load)
5 examples, 0 failures

しかしletの上書きを多用すると、Specのコード行数が多くなったときに、どのletが最終的に使われるのかわかりづらくなるという難点があります。
適切なバランスで利用する必要があります、

終わりに

ここまででSystem Specを書くために必要となる基本知識を学び、タスク管理アプリの一覧表示機能、詳細表示機能、新規作成機能について簡単なSystem Specを作成しました。

今回でRailsの自動テストについてのまとめは最後になります。

コメント

タイトルとURLをコピーしました