给定一个标准,两个对象之间有许多关系.举个简单的例子,让我们来看一下:

class Order < ActiveRecord::Base
  has_many :line_items
end

class LineItem < ActiveRecord::Base
  belongs_to :order
end

我想做的是生成一个带有存根行项目列表的存根订单.

FactoryGirl.define do
  factory :line_item do
    name 'An Item'
    quantity 1
  end
end

FactoryGirl.define do
  factory :order do
    ignore do
      line_items_count 1
    end

    after(:stub) do |order, evaluator|
      order.line_items = build_stubbed_list(:line_item, evaluator.line_items_count, :order => order)
    end
  end
end

上面的代码不起作用,因为Rails希望在分配了第_行项目且FactoryGirl引发异常时调用订单上的save:

那么,在has_may集合也是存根的情况下,如何(或是否可能)生成存根对象呢?

推荐答案

TL;博士

FactoryGirl试图通过做一个非常大的假设来提供帮助

不幸的是,ActiveRecord用这个来决定是否应该这样做

请try 将RSpec存根/模拟填充到FactoryGirl工厂.

RSpec模拟只能在规范的某些部分使用

如果你看一下将RSpec纳入say的文档

这里有几个选项:

  • 不要使用FactoryGirl创建存根;使用存根库

    如果想在FactoryGirl中保留模型属性逻辑,那没关系.

    stub_data = attributes_for(:order)
    stub_data[:line_items] = Array.new(5){
      double(LineItem, attributes_for(:line_item))
    }
    order_stub = double(Order, stub_data)
    

    是的,您必须手动创建关联.这不是坏事,

  • 清除id号区域

    after(:stub) do |order, evaluator|
      order.id = nil
      order.line_items = build_stubbed_list(
        :line_item,
        evaluator.line_items_count,
        order: order
      )
    end
    
  • 创建自己对new_record?的定义

    factory :order do
      ignore do
        line_items_count 1
        new_record true
      end
    
      after(:stub) do |order, evaluator|
        order.define_singleton_method(:new_record?) do
          evaluator.new_record
        end
        order.line_items = build_stubbed_list(
          :line_item,
          evaluator.line_items_count,
          order: order
        )
      end
    end
    

这是怎么回事?

在我看来,try 创建一个"存根"has_many通常不是一个好主意

女孩,我们需要了解的是什么

  • 数据库持久层/gem(即ActiveRecordMongoid、,
  • 任何存根/模拟库(mintest/mocks、rspec、mocha等)
  • 模拟/存根的用途

数据库持久层

每个数据库持久层的行为都不同.事实上,很多人都表现得很好

Assumption:我猜你在剩下的时间里用的是ActiveRecord

在我写这篇文章时,ActiveRecord的当前GA版本是4.1.0.什么时候

这在较旧的AR版本中也略有不同.这在中国是非常不同的

你可能会想:"但我可以用存根设置反向"

FactoryGirl.define do
  factory :line_item do
    association :order, factory: :order, strategy: :stub
  end
end

li = build_stubbed(:line_item)

没错.虽然只是因为AR决定了not to

现在,你可能会想:"我完全可以在has_many个对象中添加对象,而无需

order = Order.new
li = order.line_items.build(name: 'test')
puts LineItem.count                   # => 0
puts Order.count                      # => 0
puts order.line_items.size            # => 1

li = LineItem.new(name: 'bar')
order.line_items << li
puts LineItem.count                   # => 0
puts Order.count                      # => 0
puts order.line_items.size            # => 2

li = LineItem.new(name: 'foo')
order.line_items.concat(li)
puts LineItem.count                   # => 0
puts Order.count                      # => 0
puts order.line_items.size            # => 3

order = Order.new
order.line_items = Array.new(5){ |n| LineItem.new(name: "test#{n}") }
puts LineItem.count                   # => 0
puts Order.count                      # => 0
puts order.line_items.size            # => 5

是的,但这里order.line_items真的是一个

FactoryGirl在这里真正能做的就是让底层阶级的行为

FactoryGirl确实try 在保存对象方面提供一些帮助.这主要是

...[a factory]首先保存关联,这样外键就可以正确地

等待你可能已经注意到,在上面的例子中,我遗漏了以下内容:

order = Order.new
order.line_items = Array.new(5){ |n| LineItem.new(name: "test#{n}") }
puts LineItem.count                   # => 0
puts Order.count                      # => 0
puts order.line_items.size            # => 5

没错.我们可以将order.line_items=设为一个数组,但它不是

存根/模拟库

有许多不同的类型,FactoryGirl都与之合作.为什么?

记住,在test library of choice中添加FactoryGirl语法.

如果FactoryGirl没有使用你喜欢的库,它在做什么?

模拟/存根的用途

在我们讨论引擎盖下的细节之前,我们需要定义what

Stubs对考试期间拨打的电话提供固定答案,通常不提供

这与"模仿"有着微妙的区别:

Mocks...: 预先编程的对象,其期望值构成

存根可以作为一种建立合作者的方式,提供固定的回应.坚持

无需任何存根即可轻松创建自己的库:

stubbed_object = Object.new
stubbed_object.define_singleton_method(:name) { 'Stubbly' }
stubbed_object.define_singleton_method(:quantity) { 123 }

stubbed_object.name       # => 'Stubbly'
stubbed_object.quantity   # => 123

因为FactoryGirl对图书馆完全不可知

查看FactoryGirl v.4.4.0的实现,我们可以看到

  • persisted?
  • new_record?
  • save
  • destroy
  • connection
  • reload
  • update_attribute
  • update_column
  • created_at

这些都是非常活跃的.但是,正如你在has_many中看到的,

Why does the 100 association not work with the FactoryGirl stub?

如上所述,ActiveRecord判断其状态以决定是否应该

def new_record?
  id.nil?
end

在我抛出一些修正之前,我想回到stub的定义:

存根为测试过程中拨打的电话提供固定答案,通常为not responding at all to anything outside what's programmed in for the test个.

FactoryGirl对存根的实现违反了这一原则.因为它没有

修正1:不要使用FactoryGirl创建存根

如果您希望创建/使用存根,请使用专用于该任务的库.自从

你甚至可以推出自己的超级简单存根工厂(是的,存在问题)

require 'ostruct'
def create_stub(stubbed_attributes)
  OpenStruct.new(stubbed_attributes)
end

FactoryGirl使创建100个模型对象变得非常容易,而实际上

此外,正如您所注意到的,FactoryGirl的"存根"抽象有点复杂

如果想在FactoryGirl中保留模型属性逻辑,那没关系.

stub_data = attributes_for(:order)
stub_data[:line_items] = Array.new(5){
  double(LineItem, attributes_for(:line_item))
}
order_stub = double(Order, stub_data)

是的,您必须手动设置关联.虽然你只会设置

有一个真正的存根库有助于明确说明这一点.

同样的道理也适用于那些被称为单一对象的长链方法,

Fix #2: 清除id号区域

这更像是黑客行为.我们知道默认存根设置为id.因此,我们

after(:stub) do |order, evaluator|
  order.id = nil
  order.line_items = build_stubbed_list(
    :line_item,
    evaluator.line_items_count,
    order: order
  )
end

我们永远不会有一个存根返回id并设置has_many

Fix #3: 创建自己对new_record?的定义

在这里,我们将id的概念与存根是

module SettableNewRecord
  def new_record?
    @new_record
  end

  def new_record=(state)
    @new_record = !!state
  end
end

factory :order do
  ignore do
    line_items_count 1
    new_record true
  end

  after(:stub) do |order, evaluator|
    order.singleton_class.prepend(SettableNewRecord)
    order.new_record = evaluator.new_record
    order.line_items = build_stubbed_list(
      :line_item,
      evaluator.line_items_count,
      order: order
    )
  end
end

我们仍然需要 for each 模型手动添加它.

Ruby-on-rails相关问答推荐

Rails 7.1中的覆盖脚手架控制器

我try 使用Ruby on Rails7创建一个Carbon 足迹计算器.但我无法保存和用户S对象

RubyOnRail在测试环境中启动失败

Rails + Turbo_stream自定义操作:我可以在没有Stimulus的情况下根据DOM状态做出有条件的响应吗?

在 POST 请求中添加了 Rails 奇怪的参数

Ruby on Rails form_with 不显示错误,卡在服务器端

Ruby on Rails/Activerecord 排序自引用查询?

Rails 3.1 assets资源 - 奇怪的开发服务

blueprint/screen.css 未预编译

如何在 Ruby on Rails 中创建一个锚点并重定向到这个特定的锚点

Rails Mailer Net::OpenTimeout: execution expired 仅在生产服务器上出现异常

如何在rails中发回js.haml

设计和强大的参数

Ruby on Rails:有条件地显示部分

带有 master.key 的 Rails 5.2 - Heroku 部署

使用回形针使用 Activeadmin Rails 上传文件

form_for 未定义的方法 `user_path'

我可以在 Ubuntu 上使用 apt-get 安装 gems 吗?

您如何访问设计控制器?

如何在 Ruby 类/模块命名空间中翻译模型?