【问题标题】:Why is my Rspec test failing with duplicate name when name not unique?当名称不唯一时,为什么我的 Rspec 测试因重复名称而失败?
【发布时间】:2014-01-29 23:04:55
【问题描述】:

我有一个具有这些验证的模型:

class Tool < ActiveRecord::Base
  has_many :repairs
  has_many :services
  belongs_to :category
  belongs_to :location
  accepts_nested_attributes_for :repairs
  accepts_nested_attributes_for :services

  validates :name, :serial, :model, :presence => true
  validates_uniqueness_of :serial
end

我的工厂是这样的:

require 'faker'

FactoryGirl.define do
  factory :tool do
    association :location
    association :category
    name {Faker::Name.first_name}
    model {Faker::Company.name}
    serial {Faker::Address.latitude}
  end
end

我有一些简单的测试,比如:

it 'is valid with a name, serial, model, location, category' do

  expect(build(:tool, location: Location.new(name: 'Sta 72'),
               category: Category.new(name: 'Chainsaw'))).to be_valid

end

it 'is invalid without a name' do
  expect(build(:tool, name: nil)).to have(1).errors_on(:name)
end

it 'is invalid without a serial' do
  expect(build(:tool, serial: nil)).to have(1).errors_on(:serial)
end

it 'is invalid without a model' do
  expect(build(:tool, model: nil)).to have(1).errors_on(:model)
end

it 'is invalid with a duplicate serial' do
   tool1 = create(:tool, serial: '12345')
   tool2 = build(:tool, serial: tool1.serial)

  expect(tool2).to have(1).errors_on(:serial)
end

除了最后一次检查以确保:serial 是唯一的测试之外,一切都运行良好。它失败了:

ActiveRecord::RecordInvalid: Validation failed: Name has already been taken
./spec/models/tool_spec.rb:27:in `block (3 levels) in <top (required)>'

当我不验证 Name 的唯一性时,我不知道为什么它会抱怨 Name。第 27 行指的是tool2 = build(:tool, serial: tool1.serial)

编辑* schema.rb 看起来像这样:

...
  create_table "tools", force: true do |t|
    t.string   "name"
    t.string   "serial"
    t.date     "purchased"
    t.date     "put_in_service"
    t.decimal  "cost"
    t.decimal  "value"
    t.boolean  "in_service"
    t.date     "retired"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.text     "note"
    t.integer  "condition"
    t.text     "old_location"
    t.string   "model"
    t.boolean  "loaner",         default: false
    t.integer  "location_id"
    t.integer  "category_id"
  end

  add_index "tools", ["category_id"], name: "index_tools_on_category_id", using: :btree
  add_index "tools", ["location_id"], name: "index_tools_on_location_id", using: :btree

当我只运行失败的测试时,我的测试日志如下所示:

 ActiveRecord::SchemaMigration Load (0.6ms)  SELECT "schema_migrations".* FROM "schema_migrations"
 (0.4ms)  BEGIN
 (0.3ms)  SAVEPOINT active_record_1
SQL (9.3ms)  INSERT INTO "locations" ("created_at", "name", "type", "updated_at", "vehicle") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["created_at", Mon, 27 Jan 2014 22:28:52 PST -08:00], ["name", "Loaners"], ["type", "Station"], ["updated_at", Mon, 27 Jan 2014 22:28:52 PST -08:00], ["vehicle", true]]
 (0.3ms)  RELEASE SAVEPOINT active_record_1
 (0.3ms)  SAVEPOINT active_record_1
 Category Exists (1.5ms)  SELECT 1 AS one FROM "categories" WHERE "categories"."name" = 'Chainsaws' LIMIT 1
 SQL (1.2ms)  INSERT INTO "categories" ("created_at", "name", "updated_at") VALUES ($1, $2, $3) RETURNING "id"  [["created_at", Mon, 27 Jan 2014 22:28:52 PST -08:00], ["name", "Chainsaws"], ["updated_at", Mon, 27 Jan 2014 22:28:52 PST -08:00]]
 (0.2ms)  RELEASE SAVEPOINT active_record_1
 (0.4ms)  SAVEPOINT active_record_1
 Tool Exists (1.0ms)  SELECT 1 AS one FROM "tools" WHERE "tools"."serial" = '12345' LIMIT 1
 SQL (1.4ms)  INSERT INTO "tools" ("category_id", "created_at", "location_id", "model", "name", "serial", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id"  [["category_id", 77], ["created_at", Mon, 27 Jan 2014 22:28:52 PST -08:00], ["location_id", 43], ["model", "Stark LLC"], ["name", "Cristina"], ["serial", "12345"], ["updated_at", Mon, 27 Jan 2014 22:28:52 PST -08:00]]
 (0.2ms)  RELEASE SAVEPOINT active_record_1
 (0.3ms)  SAVEPOINT active_record_1
 SQL (0.9ms)  INSERT INTO "locations" ("created_at", "name", "type", "updated_at", "vehicle") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["created_at", Mon, 27 Jan 2014 22:28:52 PST -08:00], ["name", "Loaners"], ["type", "Station"], ["updated_at", Mon, 27 Jan 2014 22:28:52 PST -08:00], ["vehicle", true]]
 (0.3ms)  RELEASE SAVEPOINT active_record_1
 (0.3ms)  SAVEPOINT active_record_1
 Category Exists (0.5ms)  SELECT 1 AS one FROM "categories" WHERE "categories"."name" = 'Chainsaws' LIMIT 1
 (0.3ms)  ROLLBACK TO SAVEPOINT active_record_1
 (0.6ms)  ROLLBACK

编辑->

感谢下面的答案,我的问题得到了解决。我根据他们的回答使用的代码是:

it 'is invalid with a duplicate serial' do
  chainsaw = create(:category, name: 'Chainsaw')
   tool1 = create(:tool, serial: '12345', category: chainsaw)
   tool2 = build(:tool, serial: '12345', category: chainsaw)

  expect(tool2).to have(1).errors_on(:serial)
end

我不明白为什么 FactoryGirl 和 Faker 不会创建这两个工具并尊重 validates uniqueness 模型上的 validates uniqueness 约束。它不应该只是重用它刚刚为第一个工具创建的Category(而不是尝试创建一个具有相同名称的新工具),或者为与以前不同的新工具创建一个新的Category ?

啊哈!这就是为什么:

FactoryGirl.define do
  factory :category do
    name 'Chainsaws'
  end
end

我会将这个工厂设置为使用 Faker,它应该可以按我的预期工作。

【问题讨论】:

  • 你的架构是什么样的?
  • 你的location 或category 模型怎么样?它们实际上将由 FactoryGirl.build 创建。其中任何一个都具有name 唯一性吗?

标签: ruby-on-rails rspec factory-bot


【解决方案1】:

您似乎在隐式创建 Category 实例而不是 Tools 实例时遇到验证错误。

由于它发生在build(:tool, ...) 调用上,我们知道为支持build 而隐式创建的Category 实例与之前创建的Category 实例冲突。从您分享的日志中,我们可以看到前面的create(:tool, ...) 调用和build(:tool, ...) 都在创建名称为Chainsaws 的Category 实例。

(注意:此答案的早期版本错误地假设与第一个示例有冲突,但这没有意义,因为第一个示例中的名称是 Chainsaw 而不是 Chainsaws,更不用说可能性了事务很可能已启用,从而保护最后一个示例免受第一个示例的影响。)

【讨论】:

  • 谢谢,就是这样。我的类别名称是唯一的。它没有发生在第一个 create(:tool...) 上,因为它创建了第一个实例。当我去构建第二个实例时,它会尝试构建一个具有相同类别名称的新实例。这对我来说很奇怪,因为 FactoryGirl 和 Faker 不应该知道它必须构建一个不同的类别,除非被告知要使用特定名称构建它吗?我会将我的工作规范添加到问题的末尾。
  • 不是之前的调用导致了冲突,而是我 Category 的工厂没有使用 Faker。哦!
  • 嗯...不确定它回滚的点。我假设它根据before :all 和before each 保存记录。仍然让我的头脑围绕着 FactoryGirl 和 Faker。奇怪的是我有另一个测试将工厂实例保存到实例变量中,但是当我稍后在同一个it... do... end 中访问该变量时,对于任何伪造的属性,我都会得到不同的结果。它不应该创建具有假属性的假对象,并且始终是具有相同属性的相同对象吗?不知道如何或为什么要开启或关闭交易...
  • 原来我错误地认为您发布的日志是针对所有您的示例而不仅仅是最后一个,所以我在解释保存点时感到困惑。我需要查看有关实例变量等问题的详细信息,因此请将其作为单独的问题发布。最后,我更新了我的答案以更好地表达发生了什么。
【解决方案2】:

被触发的唯一性约束可能在 Category#name 上。您是否将其设置为工厂中的静态值?

# This causes the associated category to be created
tool1 = create(:tool, serial: '12345')

# The build strategy actually creates associated records.
# This is trying to create a category with a duplicate name.
tool2 = build(:tool, serial: tool1.serial)

如果您更新类别工厂以使用序列或假数据作为名称,这应该可以工作。

【讨论】:

  • 我接受了第一个答案,因为他们都是对的,而彼得的答案是第一位的。谢谢!
猜你喜欢
  • 1970-01-01
  • 2015-02-18
  • 1970-01-01
  • 2011-04-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多