当名称不唯一时,为什么我的Rspec测试失败并且名称重复?

时间:2014-01-28 05:51:22

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

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

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

编辑 - &GT;

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

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模型上的Category约束。它不应该只重用它刚为第一个工具创建的Category(而不是尝试使用相同的名称创建一个新的)或为不同的新工具创建一个新的Category比前一个?

AHA!这就是原因:

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

我将这个工厂设置为使用Faker,它应该像我期望的那样工作。

2 个答案:

答案 0 :(得分:2)

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

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

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

答案 1 :(得分:1)

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

# 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)

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