【RSpec】中途半端なデータを残してしまってエラーになる現象について&予防策と暫定的な対応

:::note alert
**当記事の解決策は最適ではないように思いますので参考程度にしてください**
:::

# はじめに
* RSpec の Capybara でシステムテストを行った際にレコードが重複して生じるエラーで困った方々に向けております

# 概要
* Rails の Web アプリを RSpec の capybara でシステムテストを行った際にエラーが生じた
* クリアしていたテスト項目でエラーが突如検知されるようになった
    * これについては「トラブルの原因」で後述

# テストで生じたエラー
```bash:実際に生じたエラー
ActiveRecord::RecordNotUnique:
PG::UniqueViolation: ERROR:  duplicate key value violates unique constraint "XXXXX_pkey"
DETAIL:  Key (id)=(1) already exists.
```
* レコードが既に存在しているよという旨なのですが、クリアしていたテスト項目に唐突にこのエラーが生じるようになりました

# 調査したこと1 - RSpec の設定関連

:::note info
* `config.use_transactional_fixtures` の設定を有効にすることで全てのテストを実行後にロールバックすることで"実質的な削除"を行っていることになる
    * `destroy` しているではなく全てのレコードをロールバックして初期化している

> ```ruby:
> RSpec.configure do |config|
>   config.use_transactional_fixtures = true
> end
> ``` 
> (直訳)「この設定の名前は少し誤解を招きやすいです。Rails で実際に意味するのは、「トランザクション内のすべてのテスト メソッドを実行する」ということです。rspec-rails のコンテキストでは、「トランザクション内のすべての例を実行する」という意味になります。
> アイデアは、各 example をクリーンなデータベースで開始し、その example に必要なデータを作成し、example の最後にトランザクションをロールバックするだけでそのデータを削除するというものです。」

引用：https://rspec.info/features/6-0/rspec-rails/Transactions/

※ここでいう `example` は `it`/`example`/`specify` の `example` と思われます
:::

* ただ私の場合、この設定を有効にしていたが `binding.pry` で確認を行った所データが残っているのが確認できた

# 調査したこと２ - `database_cleaner` という gem

:::note info
`database_cleaner` (https://github.com/DatabaseCleaner/database_cleaner)
* その名の通りデータベースをクリーンにしてテストを実行できるようにする
* `use_transactional_fixtures` は「example を最後まで実行したタイミング」なので、どういったタイミングで DB を整えるかを指定できる
:::

* ものは試しと思い導入してみたものの先のエラーが先行して私の場合は上手く機能しませんでした
    * 導入するのであれば RSpec の導入と併せてやっておくべきでした
* そもそも `use_transactional_fixtures` が細かにロールバックしてくれているので、この gem が今回のエラーの解決策にはならないと判断

# 【非推奨】解決策
:::note alert
他に解決策があるかもしれませんので、**半ば強引に解決したので参考程度にしてください**
:::

```bash:実際に生じたエラー
ActiveRecord::RecordNotUnique:
PG::UniqueViolation: ERROR:  duplicate key value violates unique constraint "XXXXX_pkey"
DETAIL:  Key (id)=(1) already exists.
```
* このエラーから分かるように「登録しようとしているデータが既に存在してしまっている」のが問題
    * これを削除してやれば良いと考えた
* `before`ブロックで指定したモデルのレコードをテスト実行前に整理した
```ruby:追加したコード
before do
User.destroy_all # テスト前にデータを削除
Post.destroy_all # テスト前にデータを削除
end
```

* 以下のように追加しました
    * 結果として無事解決
```ruby:spec/system/sample_spec.rb
require "rails_helper"

RSpec.describe "サンプルテスト", type: :system do
  before do
    User.destroy_all # テスト前にデータを削除
    Post.destroy_all # テスト前にデータを削除
  end

#####【中略】#####

end
```


# 今回のトラブルの予防策
## トラブルの原因

:::note warn
`use_transactional_fixtures` は「最後までテストを実行した後にロールバック」するため強制中断が悪手だったと推測
:::

* 私の今回の失敗として **100 件程度のテストを実行していたため<font color="Red">途中でテストを強制的に中断させてしまった</font>** のがトラブルの原因だったと思われます
    * エラーが出た時点で確認したかったため `Ctrl + C` で無理矢理中断
* これにより前回テスト時のデータが残ってしまってエラーが生じた


## RSpec 実行時の予防策

```bash:使用していたコマンド
bundle exec rspec
```

* 「テストケースが多い場合に最後まで実行し切るのを待つ」のは中々大変ですので、予防策として以下のように `--fail-fast` オプションを付ければ **テストがクリアできなかった場合に中断** してくれます

:::note info
```bash:改善後のコマンド
bundle exec rspec --fail-fast
```
:::

---
### 所感
* 当記事のエラー以外にも問題を抱えていたのですが、今回の解決策を行った所ソレも解決したため私の場合は解決策として上手く機能しました
* ただ毎度データを削除する操作をしないために RSpec がロールバックしてくれているはずなので余計なことをしているような気がする
    * そもそも削除をしないためにロールバックしているわけで

### 参考資料

https://rspec.info/features/6-0/rspec-rails/Transactions/

https://nantokapress.com/rspec-fail-fast/

https://github.com/DatabaseCleaner/database_cleaner

https://qiita.com/immrshc/items/25942acc1d1bd78ef9c3

https://stackoverflow.com/questions/71164473/delete-records-oracle-before-tests-from-ruby-capybara-and-cucumber-please

https://utahta.hatenablog.com/blog/2016/07/12/database_cleaner_%E3%81%A8_use_transactional_fixtures_%3D_true_%E3%81%AE%E7%AB%B6%E5%90%88%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6