問題
RspecでUserモデルのテストを書いている際
emailの一意性を確認するテストで
FactoryBot.create()
を使うと、以降同じテストを複数回繰り返した場合に
2回目以降のテストで以下のエラーが表示される
ActiveRecord::RecordInvalid:
Validation failed: Email has already been taken
原因
テストごとにDBのロールバックが行われていないと思われる
spec/rails_helper.rb
には以下の設定済み(デフォルト)
config.use_transactional_fixtures = true
解決
1) テストごとにDBを初期化
rails db:migrate:reset RAILS_ENV=test
以後一回に限りテストは成功
2)テストコードを書き換える
spec/factories/users.rb
FactoryBot.define do
factory :user, class: User do
username {"test_user"}
email {"test_user@mail.com"}
password {"password"}
password_confirmation {"password"}
end
factory :existed_user, class: User do
username { "existed_user" }
email { "existed_user@email.com" }
password { "password" }
password_confirmation { "password" }
end
end
spec/models/user_spec.rb
subject(:user) { FactoryBot.build(:user, email: email) }
context "emailが重複してする場合" do
existed_user = User.find_by(username: "existed_user") || FactoryBot.create(:existed_user)
let(:email) { existed_user.email }
it { is_expected.to be_invalid }
end
User.find_by(username: "existed_user") || FactoryBot.create(:existed_user)
の部分で必要な場合だけcreate
するように書き換えました
gemを使ったり他にもっと本質的な解決手段がありそうですが、
今のところ2)の方法で問題回避できております
参考
他に、factoryをシーケンシャルに定義するというのはいくつかの記事で紹介されていました
FactoryBotを使う時に覚えておきたい、たった5つのこと - Qiita
Tips: factoryにはclass定義を
また、FactoryBot.create(:existed_user)
や
FactoryBot.build(:existed_user)
とするときに
FactoryBot.define
のなかでclass: User
を指定してやらないと
以下のエラーに遭遇
NameError: uninitialized constant ExistedUser
FactoryBot.create(:user)
やFactoryBot.build(:user)
は問題ないので
勝手にUserクラスだと判断しているものと思われる
初RSpec
結果以下のようにUserモデルのテストを書いてみました
require 'rails_helper'
describe User do
describe "#create" do
it "username, email, password, password confiramationが存在すれば登録できること" do
user = FactoryBot.build(:user)
expect(user).to be_valid
end
describe "username" do
subject(:user) { FactoryBot.build(:user, username: username) }
context "usernameが存在しない場合登録できないこと" do
let(:username) { "" }
it { is_expected.to be_invalid }
end
context "usernameが12文字の場合登録できること" do
let(:username) { "a"*12 }
it { is_expected.to be_valid }
end
context "usernameが13文字の場合登録できないこと" do
let(:username) { "a"*13 }
it { is_expected.to be_invalid }
end
context "usernameが重複している場合" do
existed_user = User.find_by(username: "existed_user") || FactoryBot.create(:existed_user)
let(:username) { existed_user.username }
end
end
describe "email" do
subject(:user) { FactoryBot.build(:user, email: email) }
context "emailが存在しない場合" do
let(:email) { "" }
it { is_expected.to be_invalid }
end
context "emailが重複してする場合" do
existed_user = User.find_by(username: "existed_user") || FactoryBot.create(:existed_user)
let(:email) { existed_user.email }
it { is_expected.to be_invalid }
end
end
describe "password" do
subject(:user) { FactoryBot.build(:user, password: password, password_confirmation: password_confirmation) }
context "passwordが存在しない場合" do
let(:password) { "" }
let(:password_confirmation) { "password" }
it { is_expected.to be_invalid }
end
context "passwordが存在しても、password confirmationが存在しない場合" do
let(:password) { "passowrd" }
let(:password_confirmation) { "" }
it { is_expected.to be_invalid }
end
context "passwordがpasword_confirmationと一致しない場合" do
let(:password) { "password_a" }
let(:password_confirmation) { "password_b" }
it { is_expected.to be_invalid }
end
context "passwordが7文字の場合" do
let(:password) { "a"*7 }
let(:password_confirmation) { "a"*7 }
it { is_expected.to be_invalid }
end
context "passwordが8文字の場合" do
let(:password) { "a"*8 }
let(:password_confirmation) { "a"*8 }
it { is_expected.to be_valid }
end
end
end
end