【minitest / RSpec】繰り返し表示されている要素の中から「これ」という1件を選んで検証する方法あれこれ

## お題：こんなとき、どんなテストコードを書く？

以下で示すような質問一覧画面の中から、

> 「どのエディターを使うのが良いでしょうか」という質問に表示されている回答・コメント数は2件であること

を検証したい。どのようなテストコードを書けば良いか？

・・・というお題に対して、僕が考える解答例をいくつか提示してみる。

![Screen Shot 2022-01-28 at 19.06.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7465/ca7b751a-83d4-4ecb-73ba-a2d4203add91.png)

なお、この画面のHTMLは以下のようになっている。

### 繰り返し要素の各枠は `.thread-list-item` 

![Screen Shot 2022-01-28 at 20.57.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7465/2a441abd-46ad-cff2-abb7-8b62ab24db55.png)

### 回答・コメントの表示部分は `.thread-list-item-comment`

![Screen Shot 2022-01-28 at 19.13.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7465/8b650010-14ed-1d00-87d7-0df19b4507ed.png)

### この記事の対象となるフレームワーク

- Ruby on Rails
- minitest または RSpec

なお、この記事のHTMLではtableタグは使っていないが、表形式で表示されている場合は次のように読み替えれば同じ考え方で対処できる。

- `.thread-list-item` = trタグ
- `.thread-list-item-comment` = tdタグ内に表示されているテキスト

## 解法1：ループを回して目的の要素を探し当てる

ループで探し出すぶん、ちょっとパフォーマンスが悪くなる恐れがある。

```ruby
element = all('.thread-list-item').find { |element| element.has_text?('どのエディターを使うのが良いでしょうか') }
within element do
  # minitest
  assert_selector '.thread-list-item', text: '回答コメント (2)'
  # RSpec
  expect(page).to have_selector '.thread-list-item', text: '回答コメント (2)'
end
```

## 解法2：上から数えてn番目を決め打ちする

繰り返し要素が増減したり、時と場合によって並び順が変わったりしないことが前提。

```ruby
within all('.thread-list-item')[4] do
  # minitest
  assert_selector '.thread-list-item', text: '回答コメント (2)'
  # RSpec
  expect(page).to have_selector '.thread-list-item', text: '回答コメント (2)'

  # Tips: 質問タイトルもあわせて検証しておけば、もし表示順が変わってしまったときも検知できる
  assert_text 'どのエディターを使うのが良いでしょうか'
  # もしくは
  expect(page).to have_text 'どのエディターを使うのが良いでしょうか'
end
```

## 解法3：他のレコードを消して表示を1件だけにする

検索条件が指定できる画面であれば、対象のレコードだけが表示されるようにする。もしくは、他のレコードをDELETEしてから画面を表示する。テストする上で必ずしも他の要素も同時に表示する必要がない場合に有効。

```ruby 
# 他のデータを消してから画面を表示する
Question.where.not(title: 'どのエディターを使うのが良いでしょうか').destroy_all
visit questions_path

# 1件しか表示されないので within は不要
# minitest
assert_selector '.thread-list-item', text: '回答コメント (2)'
# RSpec
expect(page).to have_selector '.thread-list-item', text: '回答コメント (2)'
```

## 解法4：viewを変更して、レコードのidを含むclass/idを付ける

viewを変更して良いなら、`question-1234`のようにレコードのIDを識別できるclass属性またはid属性を付ける。

![Screen Shot 2022-01-28 at 20.06.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7465/3a615e55-6a49-87ae-a8c6-af806d2131e6.png)

こうすれば「これ！」という1件をテストコード上でダイレクトに指定できる。個人的にはこの方法が一番オススメ。

```ruby 
within ".question-#{@question.id}" do
  # minitest
  assert_selector '.thread-list-item', text: '回答コメント (2)'
  # RSpec
  expect(page).to have_selector '.thread-list-item', text: '回答コメント (2)'
end
```

## 応用：classやidではなくテスト専用の属性を使用する
ここまでのコード例ではすべてclass属性を参照してきたが、「テストで参照する要素はテスト専用の属性を用意して、それを参照すべきだ」という考え方もある。たとえば、以下の例では解法4でclass属性の代わりに`data-test`属性を使って対象のレコードを識別できるようにした例である。

![Screen Shot 2022-01-28 at 20.20.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7465/993482b0-feeb-64f3-4811-64e33d33a37e.png)

```ruby 
# 注：本来であれば '.thread-list-item' も data-test 属性に置き換えた方が良い
within "[data-test=\"question-#{@question.id}\"]" do
  # minitest
  assert_selector '.thread-list-item', text: '回答コメント (2)'
  # RSpec
  expect(page).to have_selector '.thread-list-item', text: '回答コメント (2)'
end
```

テスト専用の属性を使うと、

- viewの都合でclass属性が変更されてもテストが壊れにくい
- デザイン上の都合ではなく、自動テストの都合で付けられた属性であることが第三者にとって明確になる

といったメリットがあるが、テストコードを書く手間が増えてしまうので、「明らかにテスト専用の属性を使った方がメリットが大きそうだ」と思うとき以外は、僕はあまり使わない。

**2022.8.6追記**
`question-1234`のような属性を作る場合は、Railsの`dom_id`メソッドを使うと便利です。
このメソッドを使うと、モデル名とレコードのidをアンダースコアでつないだ文字列が返ります。

```ruby
dom_id(@question) #=> "question_1234"
```

https://api.rubyonrails.org/classes/ActionView/RecordIdentifier.html#method-i-dom_id

また、応用的なアプローチとしてCapybaraに独自セレクタを追加する、という方法もあります。

```ruby
Capybara.add_selector(:data_selector) do
  css { |type| "[data-test='#{type}']" }
end

find(:data_selecotr, "question_#{@question.id}").click
```

詳しくは以下の記事を参照してください。

https://qiita.com/okitan/items/eb8f8e253d0811777215

## まとめ

ここで紹介した方法のどれかがきっと役に立つはず！！

### あわせて読みたい
minitestであれ、RSpecであれ、システムテストやシステムスペックを書くときはCapybaraの知識が必要になる。Capybaraの使い方を知りたいときは以下の記事が便利！

https://qiita.com/jnchito/items/607f956263c38a5fec24