LoginSignup
0
0

【Rails】テスト時にRuntimeError: UNIQUE constraint failedが発生する

Posted at

結論

test/fixture/テーブル名.ymlデータが被っていることが原因でした。

UserモデルのemailカラムにUnique制約を後から付与した結果、テストデータのemailカラムの値が被っていたため制約に違反しているというエラーが出ていたみたいです。

test/fixture/users.yaml
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html

one:
  name: MyString
  email: MyString

two:
  name: MyString
+ email: MyString2 # 被ってなければエラーは出ない
- email: MyString

参考

↑この回答がなければ多分詰んでました。

やったこと

何をやったら上のエラーが発生するのか、また私が発生した時の状況を書いておきます。

書いてる人が初心者のため、全ての情報を鵜呑みにするのはやめたほうがいいです。
貼ってあるリンクのページを見るか、自分で調べることを推奨します。

環境

  • MacOS Sonoma 14.1
  • 開発環境: Docker 25.0.3
    • Dockerイメージ: Ruby
    • コンテナの起動方法: Dev containersを使用
  • Rubyのバージョン: 3.3.0
  • Railsのバージョン: 7.1.3.2
  • データベース: SQLite(Dockerイメージについているものを使用)

Userモデルを作成

Userモデルは、Ruby on Railsの勉強のために作成しているアプリケーションで、とりあえず生成することにしたモデルです。
今のところはユーザー登録に使う予定です。

生成コマンド
# Userモデルを生成
rails g model User name:string email:string

カラムの詳細はこんな感じです。

カラム名 備考
name string ユーザー名、被ってもいい nanasi
email string メールアドレス、ユニークにしたい nanasi@example.com

すると、以下のファイルが生成されます。

  • app/models/user.rb
  • db/migrate/現在の日時_create_users.rb
  • test/fixture/users.yml
  • test/models

users.yml

この中で今回関係あるのはtest/fixture/users.ymlのみです。
このファイルは、簡単に言えばテストデータを書くことができるファイルです。

こんな感じのが生成されると思います。

test/fixture/users.yml
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html

one:
  name: MyString
  email: MyString

two:
  name: MyString
  email: MyString

細かくは読んでいないのですが、この記事がわかりやすかったので、詳しい説明はこちらを見てください。

マイグレート

マイグレートというのは、マイグレーションファイルの内容をデータベースとモデルに適用させるみたいなやつです。

以下の記事にマイグレーションファイルを使ってカラムを追加/削除する方法が書かれています。
詳しく知りたい方はこちらを見てください。

一度ここでマイグレートしておきます。

マイグレートする
rails db:migrate

この際db/schema.rbが生成されると思います。

インデックスを作成

インデックスとは、簡単に言えばデータの検索が早くなるやつです。
また、ユニーク制約を設けるときにも作ります。

下のページがわかりやすかったので、詳しく知りたい方はこちらを見てください。

インデックスの作成方法は、簡単に言えばこうです。

  1. マイグレーションファイルを作成
  2. マイグレーションファイルを、インデックスを追加する内容になるよう編集
  3. データベースをマイグレートする

マイグレーションファイルを作成

以下のコマンドを実行します。

マイグレーションファイルを作成
rails g migration User

すると、db/migrateに以下のファイルが作られます。

db/migrate/現在の日時_users.rb
class Users < ActiveRecord::Migration[7.1]
  def change
  end
end

これがマイグレーションファイルです。
このファイルを変更し、マイグレートすることで、モデルとDBに変更を加えることができます。

マイグレーションファイルを編集

インデックスの追加には、add_indexメソッドを使います。
引数にカラム名のシンボルを入れると、そのカラムにインデックスが追加されます。

ファイルを以下のように編集します。

db/migrate/日時_users.rb
class Users < ActiveRecord::Migration[7.1]
  def change
+   add_index :users, :email, unique: true
  end
end

unique: trueという箇所は、emailカラムにユニーク制約を設ける設定です。
ちなみに:usersという箇所はよくわかってません。

データベースをマイグレート

先ほどと同じく、以下のコマンドでできます。

マイグレートのコマンド
rails db:migrate

テストを実行、エラーが発生

もともとこのアプリケーションには、Userモデルとは全く関係のないコントローラーがあり、そちらの動作を確認するためのテストが書かれていました。

動作確認のため、以下のコマンドでテストを実行しました。

テストを実行
rails t

すると、以下のエラーが発生しました。

エラー内容
ERROR StaticPagesControllerTest#テスト名1 (0.15s)
Minitest::UnexpectedError:         ActiveRecord::RecordNotUnique: RuntimeError: UNIQUE constraint failed: users.email
# スタックは略

ERROR StaticPagesControllerTest#テスト名2 (0.15s)
Minitest::UnexpectedError:         ActiveRecord::RecordNotUnique: RuntimeError: UNIQUE constraint failed: users.email
# スタックは略

# これがテストの個数分(7個)続く

エラー内容

エラーメッセージにはUNIQUE constraint failed: users.emailと書かれています。
これを日本語にすると「users.emailがユニーク制約に違反している」です(多分)。

確かにemailカラムにはユニーク制約を追加しましたが、一体どういうことでしょうか?

データベースの内容を見る

とりあえず、データベースの内容を見てみます。
dbconsoleを使ってデータベースにSELECTクエリを叩いてみました。

ターミナル
rails dbconosle

sqlite>
SELECT * FROM users;
1|test|test@sample.com|日付|日付
.quit

>dockerのプロンプト

見ての通り、そもそもデータが1つしかないので、ユニーク制約違反など起こしようがありません。

サーバーを建ててアクセスしてみる

テストではない場所ならどうなのかということで、開発環境で試してみました。

私の場合、あらかじめユーザー一覧をJSON形式で取得するAPIを用意しているので、特に準備は不要でした。

rails sコマンドでサーバーを立ち上げます。
そして、/users(前述のAPI)にアクセスします。

ステータスコードは200で、以下のようなレスポンスが返ってきました。

レスポンス
[
    {
        "id": 1,
        "name": "admin",
        "email": "test@sample.com",
        "created_at": "日付",
        "updated_at": "日付"
    }
]

しっかり取得できているみたいです。

データをDBに追加してみる

ここで、本当にユニーク制約が動作しているのか見るため、データをDBに追加してみました。

ターミナル
irb

>irb
user = User.new
user.name = '被ってないよ'
user.email = 'test@sample.com' # 被ってる
user.save

# なんか失敗してる

emailが被っているデータは追加できないみたいです。
ここは意図した通りに動作しています。

エラー文をコピペして調べる

ここでやっとエラー文をコピペして調べることにしました。
我ながら遅いですね。

そして、先ほども書いた以下のStackOverflowの質問を見つけました。

どうやらtest/fixture/users.ymlを編集するといいみたいです。
このファイルはなんなのか?という疑問を持ったため調べたら、先ほども書いたこの記事に出会いました。

users.ymlを修正する

もとのusers.ymlは、以下のようにemailカラムの値がどちらもMyStringになっていました。

もとのusers.yml
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html

one:
  name: MyString
  email: MyString

two:
  name: MyString
  email: MyString

おそらく、テストを実行しようとした際に上のテストデータをDBに登録しようとし、その結果ユニーク制約に違反してしまってエラーが出た、ということだと思います。
これは詰まるって...

ということで、どうせならテストデータ風にusers.ymlを編集してみました。
最終的に出来上がったのがこちらです。

yaml/test/fixture/users.yml
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html

# ここの値を被せるとキー制約に違反する

normal:
  name: nanasi
  email: nanasi@sample.com

error:
  name: ''
  email: 'not_email'

テストを実行する

ということで、テストを実行します。

テストを実行
rails t

Running 7 tests in a single process (parallelization threshold is 50)
Started with run options --seed 42455

  7/7: [==================================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.39507s
7 tests, 11 assertions, 0 failures, 0 errors, 0 skips

ハイライトでは赤くなってますが(実際には緑)、無事にテストが成功しています。
よかったー...

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0