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

未分類

はじめに

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

処理の共通化

beforeを使った共通化

前回作成した一覧表示機能のSpecに「ユーザーAがログインしているとき」「ユーザーBがログインしているとき」という2つのcontextを記述しました。

この2つのcontextのbeforeは以下のようになっています。

spec/system/tasks_spec.rb

before do
 visit login_path
 fill_in 'メールアドレス', with: 'a@example.com'
 fill_in 'パスワード', with: 'password'
 click_button 'ログインする'
end
before do
 FactoryBot.create(:user, name: 'ユーザーB', email: 'b@example.com')
 visit login_path
 fill_in 'メールアドレス', with: 'b@example.com'
 fill_in 'パスワード', with: 'password'
 click_button 'ログインする'
end

どちらのbeforeでもログイン処理を行なっています。入力するメールアドレスに違いはあるものの、コード自体の共通点は多いです。この類似したコードを共通化します。

Specでは同じ階層のdescribe/context内で共通する処理については、そのdescribe/contextの1つ上の階層のbefore内に処理を記述することで共通化できます。

まずは「ユーザーAがログインしているとき」というcontextで実行していたログイン処理を上の階層のbefore内に移動させて、「ユーザーBがログインしているとき」というcontextのbefore内のログイン処理を取り除きます。

spec/system/tasks_spec.rb

require 'rails_helper'

describe 'タスク管理機能', type: :system do
  describe '一覧表示機能' do
    before do
      user_a = FactoryBot.create(:user, name: 'ユーザーA', email: 'a@example.com')
      FactoryBot.create(:task, name: '最初のタスク', user: user_a)

      visit login_path
      fill_in 'メールアドレス', with: 'a@example.com'
      fill_in 'パスワード', with: 'password'
      click_button 'ログインする'
    end

    context 'ユーザーAがログインしている時' do
      it 'ユーザーAが作成したタスクが表示される' do
        expect(page).to have_content '最初のタスク'
      end
    end

    context 'ユーザーBがログインしているとき' do
      before do
        FactoryBot.create(:user, name: 'ユーザーB', email: 'b@example.com')
      end

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

このままではユーザーBのテストに関してもユーザーAでログインしてしまうため不完全です。

この問題を解決するために、共通のログイン処理中の「誰が」ログインするかという部分を「空欄」のようにして、この「空欄」を埋めるコードだけをユーザーA側とユーザーB側それぞれに配置します。

先に処理される外側に共通処理を記述し、内側の複数のdescribe/contextごとに細かな違いを定義するために「let」を使用します。

letを使った共通化

以下のletを使った記法で、変数のように使えるオブジェクトを定義できます。

let(定義名) { 定義の内容 }

コードのイメージは以下の通りです。login_userというletをSpecのあるdescribe/context野中で定義する場合、その外側でも内側でも、login_userを変数のように呼び出して使用できます。

describ '・・・' do
 before do
  # ここでlogin_userを使える
 end

 context '・・・' do
  let(:login_user) { FactoryBot.create(:user) }

  it '・・・' do
   # ここでlogin_userを使える
  end

  context '・・・' do
   before do
    # ここでlogin_userを使える
   end

   it '・・・' do
    # ここでlogin_userを使える
   end
  end
 end
end

上記を参考にspec/system/tasks_spec.rbを以下のように修正します。

spec/system/tasks_spec.rb

require 'rails_helper'

describe 'タスク管理機能', type: :system do
  describe '一覧表示機能' 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') } // ①

    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

    context 'ユーザーAがログインしている時' do
      let(:login_user) { user_a } // ④

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

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

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

上記のコードの内容は以下のようになっています。

  • ①…ユーザーAとユーザーBをletで定義しています。
  • ②…①で定義したuser_aを利用してタスクを作成します。
  • ③…ここから続く行は共通化するログイン処理です。メールアドレスやパスワードを、内側のcontextないで定義されているlogin_userから取得しています。これにより誰がログインするかに関わらず同じコードを使えます。
  • ④⑤…③で使うlogin_userをletで定義しています。

それではテストを実行してみます。

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

Finished in 11.32 seconds (files took 7.05 seconds to load)
2 examples, 0 failures

無事成功しました。

以上で処理の共通化ができました。

letについて

先ほど紹介したletは呼び出されたタイミングで初めて実行されます。
そのためlogin_userというletを定義したとしても、login_userを一度も呼び出さずにログインすると、ユーザーが作られずログインに失敗します。

describe '一覧表示機能' do
    let(:login_user) { FactoryBot.create(:user, name: 'ユーザーA', email: 'a@example.com') }

    before do
      FactoryBot.create(:task, name: '最初のタスク', user: user_a)
      visit login_path
      fill_in 'メールアドレス', with: 'a@example.com'
      fill_in 'パスワード', with: 'password'
      click_button 'ログインする' #=>失敗
    end
  ・・・
end

このような場合letの代わりにlet!を利用するとbeforeの前にユーザーが登録され、ログインできるようになります。let!は呼び出される場合と呼び出されない場合があるがデータは常に作りたい時などに便利に利用できます。

describe '一覧表示機能' do
    let!(:login_user) { FactoryBot.create(:user, name: 'ユーザーA', email: 'a@example.com') }

    before do
      FactoryBot.create(:task, name: '最初のタスク', user: user_a)
      visit login_path
      fill_in 'メールアドレス', with: 'a@example.com'
      fill_in 'パスワード', with: 'password'
      click_button 'ログインする' #=>成功
    end
  ・・・
end

タスク詳細表示機能のSystem Spec

タスク詳細表示機能のSpecを作ります。

spec/system/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

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

      it 'ユーザーAが作成したタスクが表示される' do
        expect(page).to have_content '最初のタスク'
      end
    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 'ユーザーAが作成したタスクが表示される' do
        expect(page).to have_content '最初のタスク'
      end
    end
  end
end

詳細表示機能もユーザーのログインを前提としているため、一覧表示機能のdescribe内にあったログイン処理のbeforeを上の階層に移動しました。

さらにユーザーAのタスク登録にはlet!を利用しています。

テストを実行してみます。

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

Finished in 4.52 seconds (files took 2.7 seconds to load)
3 examples, 0 failures

無事成功しました。

終わりに

今回はSpec内の処理の共通化をした後、前回に続きSystem Specを追加しました。

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

コメント

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