Ruby on rails FactoryGirl建立了一个多关联的存根策略
给定一个标准,两个对象之间有许多关系。举个简单的例子,让我们来看一下:Ruby on rails FactoryGirl建立了一个多关联的存根策略,ruby-on-rails,factory-bot,Ruby On Rails,Factory Bot,给定一个标准,两个对象之间有许多关系。举个简单的例子,让我们来看一下: class Order < ActiveRecord::Base has_many :line_items end class LineItem < ActiveRecord::Base belongs_to :order end 上述代码不起作用,因为Rails希望在分配了第_行项目且FactoryGirl引发异常时调用订单上的save: 运行时错误:不允许存根模型访问数据库 那么,如何(或是否可能
class Order < ActiveRecord::Base
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :order
end
上述代码不起作用,因为Rails希望在分配了第_行项目且FactoryGirl引发异常时调用订单上的save:
运行时错误:不允许存根模型访问数据库
那么,如何(或是否可能)在其has_may集合也是存根的情况下生成存根对象呢?我看到了这个答案,但遇到了与您相同的问题: 我发现的最干净的方法是显式地剔除关联调用
require 'rspec/mocks/standalone'
FactoryGirl.define do
factory :order do
ignore do
line_items_count 1
end
after(:stub) do |order, evaluator|
order.stub(line_items).and_return(FactoryGirl.build_stubbed_list(:line_item, evaluator.line_items_count, :order => order))
end
end
end
希望有帮助 我发现Bryce的解决方案是最优雅的,但它对新的
allow()
语法产生了一个弃用警告
为了使用新的(更干净的)语法,我做了以下操作:
2014年5月6日更新:我的第一个建议是使用私有api方法,多亏Aaraon K提供了更好的解决方案,请阅读评论以便进一步讨论
TL;博士
FactoryGirl试图通过做一个非常大的假设来提供帮助
创建它的“存根”对象。即:
不幸的是,ActiveRecord使用它来决定是否应该这样做
.
因此存根模型尝试将记录持久化到数据库中
请不要尝试将RSpec存根/模拟填充到FactoryGirl工厂中。
这样做会在同一个对象上混合两种不同的存根哲学。挑
一个或另一个
RSpec模拟只应在规范的某些部分使用
生命周期把他们搬进工厂会创造一个
隐藏违反设计的行为。由此产生的错误将是
令人困惑且难以追踪
如果您查看将RSpec包括在say中的文档
,
您可以看到,它提供了一些方法来确保模拟正确地进行
设置并在测试之间拆除。把模型放进工厂
不提供此类保证,以确保这将发生
这里有几个选项:
- 不要使用FactoryGirl创建存根;使用存根库
(rspec模拟、小型测试/模拟、摩卡、flexmock、rr等)
如果您想在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)
是的,您必须手动创建关联。这不是坏事, 请参阅下面的进一步讨论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
- 创建自己的
新记录定义?
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
有很多
与FactoryGirl的关联。这往往会导致更紧密耦合的代码
并且可能会不必要地创建许多嵌套对象
为了了解这个职位,以及FactoryGirl的现状,我们需要
请看几件事:
- 数据库持久层/gem(即
ActiveRecord
,Mongoid
,
DataMapper
,ROM
等)
- 任何存根/模拟库(mintest/mocks、rspec、mocha等)
- 模拟/存根的用途
数据库持久层
每个数据库持久性层的行为都不同。事实上,很多人都表现得很好
不同的主要版本。工厂女孩尽量不做假设
关于如何设置该层。这使他们在整个过程中具有最大的灵活性
长途运输
假设:我猜您正在使用ActiveRecord
来处理剩余的
这是一次讨论
在我写这篇文章时,ActiveRecord
的当前GA版本是4.1.0。什么时候
您设置了一个有许多关联的,
这在较旧的AR版本中也略有不同。这在中国很不一样
Mongoid等。期望FactoryGirl理解
所有这些宝石的复杂性,也没有版本之间的差异。就这样
碰巧
试图
你可能会想:“但我可以用存根设置反向”
没错。虽然这只是因为阿尔决定
.
事实证明,这种行为是一件好事。否则,这将是非常困难的
很难在不频繁访问数据库的情况下设置临时对象。
此外,它允许在一个文件中保存多个对象
事务,如果出现问题,则回滚整个事务
现在,您可能会想:“我完全可以在没有
“点击数据库”
是的,没错。我们可以将order.line\u items=
设置为数组,但它不是
坚持!那么是什么原因呢
存根/模拟库
有许多不同的类型,FactoryGirl都与之合作。为什么?
因为FactoryGirl对他们都不做任何事。完全是
不知道你有哪个图书馆
请记住,您将FactoryGirl语法添加到了。
您不会将库添加到FactoryGirl
所以,如果FactoryGirl没有使用您喜欢的库,它在做什么
模拟/存根的用途
在我们讨论引擎盖下的细节之前,我们需要定义
及其:
存根为测试期间拨打的电话提供固定答案,通常不提供
对测试程序之外的任何东西都没有反应。
存根还可以记录有关呼叫的信息,例如电子邮件网关存根
它会记住它“发送”的消息,或者只记得它发送了多少消息
“发送”
这与“模拟”有着微妙的区别:
模拟…:使用预期值预先编程的对象,形成模拟
指定他们预期接收的呼叫
存根是一种通过固定的响应来设置协作者的方法。坚持
只有科尔
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
FactoryGirl.define do
factory :line_item do
association :order, factory: :order, strategy: :stub
end
end
li = build_stubbed(:line_item)
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 = 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
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
def new_record?
id.nil?
end
require 'ostruct'
def create_stub(stubbed_attributes)
OpenStruct.new(stubbed_attributes)
end
stub_data = attributes_for(:order)
stub_data[:line_items] = Array.new(5){
double(LineItem, attributes_for(:line_item))
}
order_stub = double(Order, stub_data)
after(:stub) do |order, evaluator|
order.id = nil
order.line_items = build_stubbed_list(
:line_item,
evaluator.line_items_count,
order: order
)
end
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