ツイート投稿の結合テスト

ツイート投稿に関するexampleおよびその中での挙動を洗い出すと、以下のようになります。

  • ツイート投稿ができるとき
    • ログインしたユーザーは新規投稿できる
    • ログインする
    • 新規投稿ページへのボタンがあることを確認する
    • 投稿ページに移動する
    • フォームに情報を入力する
    • 送信するとTweetモデルのカウントが1上がることを確認する
    • 投稿完了ページに遷移することを確認する
    • 「投稿が完了しました」の文字があることを確認する
    • トップページに遷移する
    • トップページには先ほど投稿した内容のツイートが存在することを確認する(画像)
    • トップページには先ほど投稿した内容のツイートが存在することを確認する(テキスト)
  • ツイート投稿ができないとき
    • ログインしていないと新規投稿ページに遷移できない
    • トップページに遷移する
    • 新規投稿ページへのボタンがないことを確認する

ツイートの投稿は、ログインしているユーザーしかできません。その点に注意してexampleを洗い出します。
それではこちらをテストコードに落とし込みましょう。

 

spec/system/tweets_spec.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
require 'rails_helper'

RSpec.describe 'ツイート投稿', type: :system do
  before do
    @user = FactoryBot.create(:user)
    @tweet_text = Faker::Lorem.sentence
    @tweet_image = Faker::Lorem.sentence
  end
  context 'ツイート投稿ができるとき'do
    it 'ログインしたユーザーは新規投稿できる' do
      # ログインする
      # 新規投稿ページへのボタンがあることを確認する
      # 投稿ページに移動する
      # フォームに情報を入力する
      # 送信するとTweetモデルのカウントが1上がることを確認する
      # 投稿完了ページに遷移することを確認する
      # 「投稿が完了しました」の文字があることを確認する
      # トップページに遷移する
      # トップページには先ほど投稿した内容のツイートが存在することを確認する(画像)
      # トップページには先ほど投稿した内容のツイートが存在することを確認する(テキスト)
    end
  end
  context 'ツイート投稿ができないとき'do
    it 'ログインしていないと新規投稿ページに遷移できない' do
      # トップページに遷移する
      # 新規投稿ページへのボタンがないことを確認する
    end
  end
end

beforeにおけるポイントは以下の2点です。

  • ユーザーはあらかじめ保存しておくこと
  • @tweet_text@tweet_imageという、ツイートに投稿した文字をあらかじめ生成してインスタンス変数に代入すること

 

<div class="content_post" style="background-image: url(画像のURL);">という要素を取得できれば、投稿した画像が表示されているか確認できそうです。

この時に使用するものが、have_selectorマッチャです。

 

have_selectorハブ セレクタ

指定したセレクタが存在するかどうかを判断するマッチャです。上記の例であればhave_selector ".content_post[style='background-image: url(#{@tweet_image});']"という形で記述できます。

tweets_spec.rbを以下のように編集しましょう。

spec/system/tweets_spec.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
context 'ツイート投稿ができるとき'do
  it 'ログインしたユーザーは新規投稿できる' do
    # ログインする
    visit new_user_session_path
    fill_in 'Email', with: @user.email
    fill_in 'Password', with: @user.password
    find('input[name="commit"]').click
    expect(current_path).to eq(root_path)
    # 新規投稿ページへのボタンがあることを確認する
    expect(page).to have_content('投稿する')
    # 投稿ページに移動する
    visit new_tweet_path
    # フォームに情報を入力する
    fill_in 'tweet_image', with: @tweet_image
    fill_in 'tweet_text', with: @tweet_text
    # 送信するとTweetモデルのカウントが1上がることを確認する
    expect{
      find('input[name="commit"]').click
    }.to change { Tweet.count }.by(1)
    # 投稿完了ページに遷移することを確認する
    expect(current_path).to eq(tweets_path)
    # 「投稿が完了しました」の文字があることを確認する
    expect(page).to have_content('投稿が完了しました。')
    # トップページに遷移する
    visit root_path
    # トップページには先ほど投稿した内容のツイートが存在することを確認する(画像)
    expect(page).to have_selector ".content_post[style='background-image: url(#{@tweet_image});']"
    # トップページには先ほど投稿した内容のツイートが存在することを確認する(テキスト)
    expect(page).to have_content(@tweet_text)
  end
end

ツイート投稿ができないときのテストコードを書きましょう

tweets_spec.rbを以下のように編集しましょう。

spec/system/tweets_spec.rb
1
2
3
4
5
6
7
8
context 'ツイート投稿ができないとき'do
  it 'ログインしていないと新規投稿ページに遷移できない' do
    # トップページに遷移する
    visit root_path
    # 新規投稿ページへのボタンがないことを確認する
    expect(page).to have_no_content('投稿する')
  end
end

 

ツイート編集に関するexampleおよびその中での挙動を洗い出すと、以下のようになります。

  • ツイート編集ができるとき
    • ログインしたユーザーは自分が投稿したツイートの編集ができる
    • ツイート1を投稿したユーザーでログインする
    • ツイート1に「編集」へのリンクがあることを確認する
    • 編集ページへ遷移する
    • すでに投稿済みの内容がフォームに入っていることを確認する
    • 投稿内容を編集する
    • 編集してもTweetモデルのカウントは変わらないことを確認する
    • 編集完了画面に遷移したことを確認する
    • 「更新が完了しました」の文字があることを確認する
    • トップページに遷移する
    • トップページには先ほど変更した内容のツイートが存在することを確認する(画像)
    • トップページには先ほど変更した内容のツイートが存在することを確認する(テキスト)
  • ツイート編集ができないとき
    • ログインしたユーザーは自分以外が投稿したツイートの編集画面には遷移できない
    • ツイート1を投稿したユーザーでログインする
    • ツイート2に「編集」へのリンクがないことを確認する
    • ログインしていないとツイートの編集画面には遷移できない
    • トップページにいる
    • ツイート1に「編集」へのリンクがないことを確認する
    • ツイート2に「編集」へのリンクがないことを確認する

ポイントは、「ログインしていたとしても他のユーザーのツイート編集ボタンはクリックできない」 という点です。それを確かめるために、それぞれ別のユーザーに紐づくツイート1とツイート2を作成し、その挙動を確かめます。

spec/system/tweets_spec.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
require 'rails_helper'

# ツイート投稿の結合テストコードは省略しています
# ツイート投稿の結合テストコードに続いて記述しましょう

RSpec.describe 'ツイート編集', type: :system do
  before do
    @tweet1 = FactoryBot.create(:tweet)
    @tweet2 = FactoryBot.create(:tweet)
  end
  context 'ツイート編集ができるとき' do
    it 'ログインしたユーザーは自分が投稿したツイートの編集ができる' do
      # ツイート1を投稿したユーザーでログインする
      # ツイート1に「編集」へのリンクがあることを確認する
      # 編集ページへ遷移する
      # すでに投稿済みの内容がフォームに入っていることを確認する
      # 投稿内容を編集する
      # 編集してもTweetモデルのカウントは変わらないことを確認する
      # 編集完了画面に遷移したことを確認する
      # 「更新が完了しました」の文字があることを確認する
      # トップページに遷移する
      # トップページには先ほど変更した内容のツイートが存在することを確認する(画像)
      # トップページには先ほど変更した内容のツイートが存在することを確認する(テキスト)
    end
  end
  context 'ツイート編集ができないとき' do
    it 'ログインしたユーザーは自分以外が投稿したツイートの編集画面には遷移できない' do
      # ツイート1を投稿したユーザーでログインする
      # ツイート2に「編集」へのリンクがないことを確認する
    end
    it 'ログインしていないとツイートの編集画面には遷移できない' do
      # トップページにいる
      # ツイート1に「編集」へのリンクがないことを確認する
      # ツイート2に「編集」へのリンクがないことを確認する
    end
  end
end

 

have_linkハブ リンク

expect('要素').to have_link 'ボタンの文字列', href: 'リンク先のパス'と記述することで、要素の中に当てはまるリンクがあることを確認できます。have_linkはa要素に対して用います。

have_no_linkハブ ノー リンク

have_linkの逆で、当てはまるリンクがないことを確認します。expect('要素').to have_no_link 'ボタンの文字列', href: 'リンク先のパス'と記述することで、要素の中に当てはまるリンクがないことを確認できます。

 

spec/system/tweets_spec.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
context 'ツイート編集ができるとき' do
  it 'ログインしたユーザーは自分が投稿したツイートの編集ができる' do
    # ツイート1を投稿したユーザーでログインする
    visit new_user_session_path
    fill_in 'Email', with: @tweet1.user.email
    fill_in 'Password', with: @tweet1.user.password
    find('input[name="commit"]').click
    expect(current_path).to eq(root_path)
    # ツイート1に「編集」へのリンクがあることを確認する
    expect(
      all('.more')[1].hover
    ).to have_link '編集', href: edit_tweet_path(@tweet1)
    # 編集ページへ遷移する
    visit edit_tweet_path(@tweet1)
    # すでに投稿済みの内容がフォームに入っていることを確認する
    expect(
      find('#tweet_image').value
    ).to eq(@tweet1.image)
    expect(
      find('#tweet_text').value
    ).to eq(@tweet1.text)
    # 投稿内容を編集する
    fill_in 'tweet_image', with: "#{@tweet1.image}+編集した画像URL"
    fill_in 'tweet_text', with: "#{@tweet1.text}+編集したテキスト"
    # 編集してもTweetモデルのカウントは変わらないことを確認する
    expect{
      find('input[name="commit"]').click
    }.to change { Tweet.count }.by(0)
    # 編集完了画面に遷移したことを確認する
    expect(current_path).to eq(tweet_path(@tweet1))
    # 「更新が完了しました」の文字があることを確認する
    expect(page).to have_content('更新が完了しました。')
    # トップページに遷移する
    visit root_path
    # トップページには先ほど変更した内容のツイートが存在することを確認する(画像)
    expect(page).to have_selector ".content_post[style='background-image: url(#{@tweet1.image}+編集した画像URL);']"
    # トップページには先ほど変更した内容のツイートが存在することを確認する(テキスト)
    expect(page).to have_content("#{@tweet1.text}+編集したテキスト")
  end
end

10~12行目に着目しましょう。@tweet1のツイートは2番目にあるので、all('.more')[1].hoverとして2番目のツイートのmoreクラスにカーソルをあわせています。そして、

10
11
12
expect(
  all('.more')[1].hover
).to have_link '編集', href: edit_tweet_path(@tweet1)

とすることで、2つ目のツイートのmoreクラスにカーソルをあわせたときに、「編集」という@tweet1の編集へのリンクがあること を確認しています。

 

ツイート編集ができないときのテストコードを書きましょう

tweets_spec.rbを以下のように編集しましょう。

spec/system/tweets_spec.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
context 'ツイート編集ができないとき' do
  it 'ログインしたユーザーは自分以外が投稿したツイートの編集画面には遷移できない' do
    # ツイート1を投稿したユーザーでログインする
    visit new_user_session_path
    fill_in 'Email', with: @tweet1.user.email
    fill_in 'Password', with: @tweet1.user.password
    find('input[name="commit"]').click
    expect(current_path).to eq(root_path)
    # ツイート2に「編集」へのリンクがないことを確認する
    expect(
      all('.more')[0].hover
    ).to have_no_link '編集', href: edit_tweet_path(@tweet2)
  end
  it 'ログインしていないとツイートの編集画面には遷移できない' do
    # トップページにいる
    visit root_path
    # ツイート1に「編集」へのリンクがないことを確認する
    expect(
      all('.more')[1].hover
    ).to have_no_link '編集', href: edit_tweet_path(@tweet1)
    # ツイート2に「編集」へのリンクがないことを確認する
    expect(
      all('.more')[0].hover
    ).to have_no_link '編集', href: edit_tweet_path(@tweet2)
  end
end

10~12行目に着目しましょう。@tweet1のユーザーでログインしている時は、@tweet2の編集へのリンクがないことをhave_no_linkで確かめています。

また、18~24行目に着目しましょう。そもそもログインしていないときは、@tweet1と@tweet2、つまり存在するすべてのツイート編集リンクがないことを確かめています。

 

ツイート編集に関するexampleおよびその中での挙動を洗い出すと、以下のようになります。

  • ツイート削除ができるとき
    • ログインしたユーザーは自らが投稿したツイートの削除ができる
    • ツイート1を投稿したユーザーでログインする
    • ツイート1に「削除」へのリンクがあることを確認する
    • 投稿を削除するとレコードの数が1減ることを確認する
    • 削除完了画面に遷移したことを確認する
    • 「削除が完了しました」の文字があることを確認する
    • トップページに遷移する
    • トップページにはツイート1の内容が存在しないことを確認する(画像)
    • トップページにはツイート1の内容が存在しないことを確認する(テキスト)
  • ツイート削除ができないとき
    • ログインしたユーザーは自分以外が投稿したツイートの削除ができない
    • ツイート1を投稿したユーザーでログインする
    • ツイート2に「削除」へのリンクがないことを確認する
    • ログインしていないとツイートの削除ボタンがないことを確認する
    • トップページに移動する
    • ツイート1に「削除」へのリンクがないことを確認する
    • ツイート2に「削除」へのリンクがないことを確認する

こちらをテストコードに落とし込みましょう。

テストコードを編集しましょう

tweets_spec.rbを以下のように編集しましょう。

spec/system/tweets_spec.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
require 'rails_helper'

# ツイート投稿、編集の結合テストコードは省略しています
# ツイート編集の結合テストコードに続いて記述しましょう

RSpec.describe 'ツイート削除', type: :system do
  before do
    @tweet1 = FactoryBot.create(:tweet)
    @tweet2 = FactoryBot.create(:tweet)
  end
  context 'ツイート削除ができるとき' do
    it 'ログインしたユーザーは自らが投稿したツイートの削除ができる' do
      # ツイート1を投稿したユーザーでログインする
      # ツイート1に「削除」へのリンクがあることを確認する
      # 投稿を削除するとレコードの数が1減ることを確認する
      # 削除完了画面に遷移したことを確認する
      # 「削除が完了しました」の文字があることを確認する
      # トップページに遷移する
      # トップページにはツイート1の内容が存在しないことを確認する(画像)
      # トップページにはツイート1の内容が存在しないことを確認する(テキスト)
    end
  end
  context 'ツイート削除ができないとき' do
    it 'ログインしたユーザーは自分以外が投稿したツイートの削除ができない' do
      # ツイート1を投稿したユーザーでログインする
      # ツイート2に「削除」へのリンクがないことを確認する
    end
    it 'ログインしていないとツイートの削除ボタンがない' do
      # トップページに移動する
      # ツイート1に「削除」へのリンクがないことを確認する
      # ツイート2に「削除」へのリンクがないことを確認する
    end
  end
end

編集の場合は「編集ページに移動して編集ボタンをクリックする」でしたが、削除の場合は「トップページから削除リンクをクリックする」という挙動となります。

「リンクをクリックする」を再現するためには、find_link().clickを用います。

find_link().clickファインド リンク クリック

a要素で表示されているリンクをクリックするために用います。find_link('リンクの文字列', href: 'URL').clickといった形で使います。find().clickと似ていますが、find_link().clickはa要素のみに対して用いることができます。

 

spec/system/tweets_spec.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
context 'ツイート削除ができるとき' do
  it 'ログインしたユーザーは自らが投稿したツイートの削除ができる' do
    # ツイート1を投稿したユーザーでログインする
    visit new_user_session_path
    fill_in 'Email', with: @tweet1.user.email
    fill_in 'Password', with: @tweet1.user.password
    find('input[name="commit"]').click
    expect(current_path).to eq(root_path)
    # ツイート1に「削除」へのリンクがあることを確認する
    expect(
      all('.more')[1].hover
    ).to have_link '削除', href: tweet_path(@tweet1)
    # 投稿を削除するとレコードの数が1減ることを確認する
    expect{
      all('.more')[1].hover.find_link('削除', href: tweet_path(@tweet1)).click
    }.to change { Tweet.count }.by(-1)
    # 削除完了画面に遷移したことを確認する
    expect(current_path).to eq(tweet_path(@tweet1))
    # 「削除が完了しました」の文字があることを確認する
    expect(page).to have_content('削除が完了しました。')
    # トップページに遷移する
    visit root_path
    # トップページにはツイート1の内容が存在しないことを確認する(画像)
    expect(page).to have_no_selector ".content_post[style='background-image: url(#{@tweet1.image});']"
    # トップページにはツイート1の内容が存在しないことを確認する(テキスト)
    expect(page).to have_no_content("#{@tweet1.text}")
  end
end

@tweet1のユーザーでログインし、削除ボタンがあることを確認しています。そして、削除ボタンをクリックすると、レコードの数が1減ることを確かめています。

続いて、削除ができないケースのテストコードを記述しましょう。

 

ツイート削除ができないときのテストコードを書きましょう

tweets_spec.rbを以下のように編集しましょう。

spec/system/tweets_spec.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  context 'ツイート削除ができないとき' do
    it 'ログインしたユーザーは自分以外が投稿したツイートの削除ができない' do
      # ツイート1を投稿したユーザーでログインする
      visit new_user_session_path
      fill_in 'Email', with: @tweet1.user.email
      fill_in 'Password', with: @tweet1.user.password
      find('input[name="commit"]').click
      expect(current_path).to eq(root_path)
      # ツイート2に「削除」へのリンクがないことを確認する
      expect(
        all('.more')[0].hover
      ).to have_no_link '削除', href: tweet_path(@tweet2)
    end
    it 'ログインしていないとツイートの削除ボタンがない' do
      # トップページに移動する
      visit root_path
      # ツイート1に「削除」へのリンクがないことを確認する
      expect(
        all('.more')[1].hover
      ).to have_no_link '削除', href: tweet_path(@tweet1)
      # ツイート2に「削除」へのリンクがないことを確認する
      expect(
        all(".more")[0].hover
      ).to have_no_link '削除', href: tweet_path(@tweet2)
    end
  end

10~12行目に着目しましょう。@tweet1のユーザーでログインしている時は、@tweet2の削除のリンクがないことをhave_no_linkで確かめています。

また、18~24行目に着目しましょう。そもそもログインしていないときは、@tweet1と@tweet2、つまり存在するすべてのツイート削除リンクがないことを確かめています。

ツイート詳細表示に関するexampleおよびその中での挙動を洗い出すと、以下のようになります。

  • ログインしたユーザーはツイート詳細ページに遷移してコメント投稿欄が表示される
    • ログインする
    • ツイートに「詳細」へのリンクがあることを確認する
    • 詳細ページに遷移する
    • 詳細ページにツイートの内容が含まれている
    • コメント用のフォームが存在する
  • ログインしていない状態でツイート詳細ページに遷移できるもののコメント投稿欄が表示されない
    • トップページに移動する
    • ツイートに「詳細」へのリンクがあることを確認する
    • 詳細ページに遷移する
    • 詳細ページにツイートの内容が含まれている
    • フォームが存在しないことを確認する
    • 「コメントの投稿には新規登録/ログインが必要です」が表示されていることを確認する

ポイントとしては、ログインしているとき/していないときで、詳細ページにおけるコメント投稿部分に表示の違いがあります。

こちらをテストコードに落とし込みましょう。

テストコードを編集しましょう

tweets_spec.rbを以下のように編集しましょう。

spec/system/tweets_spec.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
require 'rails_helper'

# ツイート投稿、編集、削除の結合テストコードは省略しています
# ツイート削除の結合テストコードに続いて記述しましょう

RSpec.describe 'ツイート詳細', type: :system do
  before do
    @tweet = FactoryBot.create(:tweet)
  end
  it 'ログインしたユーザーはツイート詳細ページに遷移してコメント投稿欄が表示される' do
    # ログインする
    # ツイートに「詳細」へのリンクがあることを確認する
    # 詳細ページに遷移する
    # 詳細ページにツイートの内容が含まれている
    # コメント用のフォームが存在する
  end
  it 'ログインしていない状態でツイート詳細ページに遷移できるもののコメント投稿欄が表示されない' do
    # トップページに移動する
    # ツイートに「詳細」へのリンクがあることを確認する
    # 詳細ページに遷移する
    # 詳細ページにツイートの内容が含まれている
    # フォームが存在しないことを確認する
    # 「コメントの投稿には新規登録/ログインが必要です」が表示されていることを確認する
  end
end