Rspecの修正

TODO

  • ローカル変数ではなく let を使用する
    letを使用してprojectとtaskの変数を共通化し、visitの引数で呼び出す。

RSpec.describe 'Task', type: :system do
  let(:project) { create(:project) }
  let(:task) { create(:task) }
  let(:done) { create(:task, :done)}

  visit project_tasks_path(project)  
  • FactoryBotのtraitを利用する
    
 factory :task do配下でtraitを作成。
factorybotにアソシエーションを定義した。
 FactoryBot.define do
  factory :task do
    title { 'Task' }
    status { rand(2) }
    from = Date.parse("2019/08/01")
    to   = Date.parse("2019/12/31")
    deadline { Random.rand(from..to) }

    association :project
    trait :done do
      status { :done }
      completion_date { Time.current.yesterday}
    end
  end
end

letでprojectの定義が不要になるので、visit project_task_pathとvisit edit_project_task_pathの引数を変更。

...
it '既にステータスが完了のタスクのステータスを変更した場合、Taskの完了日が更新されないこと' do
  # TODO: FactoryBotのtraitを利用してください
  - task = FactoryBot.create(:task, project_id: project.id, status: :done, completion_date: Time.current.yesterday)
  - visit edit_project_task_path(project, task)
  + task_done = create(:task, :done, project_id: project.id)
  + visit edit_project_task_path(project, task_done)
...
  - expect(current_path).to eq project_task_path(project, task)
  + expect(current_path).to eq project_task_path(project, task_done)
end
...

factories/tasks.rbにアソシエーションを定義しているので、visit project_task_path(task.project, task)とすることでproject_id = 1のtaskが生成される。

RSpec.describe 'Task', type: :system do
  let(:project) { create(:project) }
  - let(:task) { create(:task, project_id: project.id) }
  + let(:task) { create(:task) }
  + let(:done) { create(:task, :done)}

  describe 'Task一覧' do
    context '正常系' do
      it '一覧ページにアクセスした場合、Taskが表示されること' do
...
  describe 'Task詳細' do
    context '正常系' do
      it 'Taskが表示されること' do
        # TODO: ローカル変数ではなく let を使用してください
        - visit project_task_path(project, task)
        + visit project_task_path(task.project, task)

元々のvisit project_task_path(project, task)から今回の形に変更したことについて

visit project_task_path(project, task)のURLヘルパーメソッドの第一引数をtask.projectとすることによって、/projects/1/tasks/1というURLを取得することができる。
これは、factories/tasks.rbにアソシエーションを定義しているため、taskに紐付いたprojectを取得するには第一引数をtask.projectとし、第二引数は第一引数で作成されたtaskが引数となるのでtaskとする必要がある。
アソシエーションを定義しているので元の形visit project_task_path(project, task)では、第一引数がprojectだと第二引数のtaskと紐付かないprojectを生成してエラーになる。

visit project_task_path(project, task)の場合

第一引数にproject、第二引数にtaskとすると、let(:project)でproject(id:1)、let(:task)でproject(id:2)が生成されるproject_id=1のtaskは存在しないため、ActiveRecord::RecordNotFound:エラーが発生する。
以下、ローカルで確認。
Failure/Error: @task = @project.tasks.find(params[:id])からtasks_controllerのset_taskメソッドで発生してることがわかる。

visit project_task_path(task, project)の場合

projectを入れる場所にtask、taskを入れる場所にprojectをいれると
第一引数ではtaskに紐付いたprojectが生成され、第二引数では第一引数のtaskとは紐付かないprojectを生成するため、taskとprojectの関連がないURLが生成される。
tasks_controllerの@task = @project.tasks.find(params[:id])でprojectと関連したtaskを見つけることができないので、ActiveRecord::RecordNotFound:エラーが発生する。

FIXME

  • Project詳細からTask一覧ページにアクセスした場合、Taskが表示されること
    
 ブラウザで確認したところ、別タブでTask一覧ページが開かれたので
switch_to_window(windows.last)で別タブでテストされるようにした。
it 'Project詳細からTask一覧ページにアクセスした場合、Taskが表示されること' do
  # FIXME: テストが失敗するので修正してください
  visit project_path(project)
  click_link 'View Todos'
+ switch_to_window(windows.last)
  expect(page).to have_content task.title
  expect(Task.count).to eq 1
  expect(current_path).to eq project_tasks_path(project)
end    
  • Taskを編集した場合、一覧画面で編集後の内容が表示されること
    
deadlineの時刻表示の形式が違うので、task.deadline.strftime('%-m-%d %H:%M'))としたいが、実装側の時刻表示の仕様が変わってもテストが失敗しないようにしたい
    Rspecヘルパーメソッドを導入しApplicationHelperで定義したメソッドをview側で使いたいときに呼び出す。
+ config.include ApplicationHelper
describe 'Task編集' do
    context '正常系' do
      it 'Taskを編集した場合、一覧画面で編集後の内容が表示されること' do
        # FIXME: テストが失敗するので修正してください
        visit edit_project_task_path(task.project, task)
        fill_in 'Deadline', with: Time.current
        click_button 'Update Task'
        click_link 'Back'
       - expect(find('.task_list')).to have_content(Time.current.strftime('%-m/%d %-H:%M'))
       + expect(find('.task_list')).to have_content(short_time(task.reload.deadline))
        expect(current_path).to eq project_tasks_path(task.project)
      end
...
  • Taskが削除されること
    expect(page).not_to have_content task.titleだとpage内にtask.titleがあるか確認する
検索範囲がページ全体なので、別の文字のTaskと一致してテストが失敗する。
    find('.task_list')で検索範囲を限定し、viewで定義しているclass = "task_list"を指定した。
...
- expect(page).not_to have_content task.title
+ expect(find('.task_list')).not_to have_content task.title
...