如何验证和测试Rails 4中关联对象的串联创建?

时间:2014-01-13 08:25:27

标签: ruby-on-rails ruby validation rspec associations

在我的应用中,初始化用户时,我希望他们构建5个项目。我已经看到断言的测试,例如,expect(@user.items.count).to eq(5)。但是,我一直在尝试验证项目的长度并测试验证本身,而不是与用户关联的对象数量。这甚至可能吗?如果是这样,最好的方法是什么?

这是我到目前为止的相关代码。

class User < ActiveRecord::Base
  ITEMS_ALLOWED = 5
  has_many :items

  validates :items, length: {is: ITEMS_ALLOWED}
  after_initialize :init_items

  def init_items
    ITEMS_ALLOWED.times { items.build }
  end
...

我的相关测试,使用RSpec,Faker和FactoryGirl

describe User do
  before :each do
    @user = build(:user, username: "bob")
    @user.save
  end

  it "is invalid with more than 5 items" do
    user3 = build(:user)
    user3.save
    expect(user3.items.create(name: "test")).not_to be_valid
  end
end

目前,测试尝试验证已创建的项目。我尝试将验证移到Item类,但我在尝试调用user.items.count的行上收到错误,未定义的nil方法项。

class Item < ActiveRecord::Base
  belongs_to :user

  validates :number_of_items, length: {is: 5}

  def number_of_items
    errors.add("User must have exactly 5 items.") unless user.items.count == 5
  end
end

===== 更新:失败消息在Item类中没有验证时。

Failures:

  1) User initialization is invalid with more than 5 items
     Failure/Error: expect(user3.items.create(name: "test")).not_to be_valid
       expected #<Item id: 16, name: "test", user_id: 3, photo: nil, created_at: "2014-01-14 00:24:11", updated_at: "2014-01-14 00:24:11", photo_file_name: nil, photo_content_type: nil, photo_file_size: nil, photo_updated_at: nil, description: nil> not to be valid

2 个答案:

答案 0 :(得分:0)

创建User实例时,正在调用init_items并正在创建Item个实例。但是,此时未定义用户的ID,因此创建的项目的user_id值为nil。这反过来导致表格的user方法在nil验证中返回number_of_items

当您删除Item验证时,那么您的RSpec示例将失败,因为您正在对Item进行验证(即user3.items.create的结果)而不是验证结果User。相反,你可以做这样的事情:

user3.items.create(name: "test")
expect(user3).to_not be_valid

答案 1 :(得分:0)

我会避免使用after_initialize。只要对象被实例化,即使仅在调用User.find之后,也会调用它。如果您必须使用它,请为new_record?添加测试,以便仅为新User添加项目。

另一种方法是编写一个使用的构建器方法,而不是User.new。

class User < ActiveRecord::Baae
  ITEMS_ALLOWED = 5
  has_many :items

  validates :items, length { is: ITEMS_ALLOWED }

  def self.build_with_items
    new.tap do |user|
      user.init_items
    end
  end

  def init_items
    ITEMS_ALLOWED.times { items.build }
  end
end

describe User do
  context "when built without items" do
    let(:user) { User.new }

    it "is invalid" do
      expect(user.items.size).to eq 0
      expect(user.valid?).to be_false
    end
  end

  context "when built with items" do
    let(:user) { User.build_with_items }

    it "is valid" do
      expect(user.items.size).to eq 5
      expect(user.valid?).to be_true
    end
  end
end

这允许您将项目初始化与用户初始化分开,以防您最终想要一个没有项目的User。根据我的经验,这比要求以相同方式构建所有新建对象更好。需要权衡的是,您现在需要在控制器中的User.build_with_items操作中使用new