如何测试仅适用于rspec中的ActiveRecord关系代理类的方法?例如,sum
类似于@collection.sum(:attribute)
以下是我要做的事情:
@invoice = stub_model(Invoice)
@line_item = stub_model(LineItem, {quantity: 1, cost: 10.00, invoice: @invoice})
@invoice.stub(:line_items).and_return([@line_item])
@invoice.line_items.sum(:cost).should eq(10)
这不起作用,因为@invoice.line_items
返回一个不像ActiveRecord :: Relation对象那样定义sum
的常规数组。
非常感谢任何帮助。
答案 0 :(得分:14)
我不确定您使用的是哪个Rails,因此我将在此示例中使用Rails 4.0.x; Rails 3.x的原则仍然适用。
TL; DR:你不想走这条路。
你正在迅速走向嘲弄/顽固的道路。我一直走在这条路上,它并没有带来乐趣。部分原因归结为违反Law of Demeter。部分原因在于使用Rails API而不是创建自己的域API。
当您从ActiveRecord
模型请求关系集合时,如您所知,它不会返回Array
。在Rails 4.0.x中,has_many
关联,返回的类是:ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Model
。
此处您的返回类型为Array
。而实际的返回类型是ActiveRecord_Associations_CollectionProxy_Model
。在存根/模拟土地中,这不一定是坏事。但是,如果您打算对存根返回的对象使用其他调用,则需要匹配相同的API协定。否则,您不会抄袭相同的行为。
在这种情况下,AR关联代理上定义的sum
方法在运行时实际执行SQL。 sum
上定义的Array
方法通过Active Support修补。 Array#sum
行为根本不同:
def sum(identity = 0, &block)
if block_given?
map(&block).sum(identity)
else
inject { |sum, element| sum + element } || identity
end
end
如您所见,它对元素求和,而不是所请求属性的总和。
您遇到的另一个主要问题是,您正在尝试规定您的存根会返回您存根的内容。这没有意义。存根的要点是返回一个固定的答案。它没有断言它的行为方式。
你所写的内容与以下内容并无根本不同:
invoice = stub_model(Invoice)
line_item = stub_model(LineItem, {quantity: 1, cost: 10.00, invoice: invoice})
invoice.stub(:line_items).and_return([line_item])
invoice.line_items.should eq([line_item])
除非这应该是一个完整性检查,否则它对您的规格没有任何实际价值。
我不确定你在这里写什么类型的规范。如果这是一个更传统的单元测试或验收测试,那么我可能不会存在任何东西。有时候点击数据库并不一定有什么问题,特别是当你测试的东西是你与它的互动方式时;这就是你在这里做的事。
您可以做的另一件事是开始使用它来创建您自己的特定域模型API。所有这些真正意味着定义对您的域有意义的对象的接口,这些接口可能会或可能不会由DB或其他资源支持。
例如,拿你的invoice.line_items.sum(:cost).should eq(10)
,这显然是测试Rails AR API。在域名方面,它没有任何意义。但是,invoice.subtotal
可能对您的域名意味着更多:
# app/models/invoice.rb
class Invoice < ActiveRecord::Base
def subtotal
line_items.sum(:cost)
end
end
# spec/models/invoice_spec.rb
# These are unit specs on the model, which directly works with the DB
# it probably doesn't make sense to stub things here
describe Invoice do
specify "the subtotal is the sum of all line item cost" do
invoice = create(:invoice)
3.times do |i|
cost = (i + 1) * 2
invoice.line_items.create(cost: cost)
end
expect(invoice.subtotal).to eq 12
end
end
现在稍后,当您在代码的其他部分使用Invoice
时,如果需要,可以轻松地将其存根:
# spec/helpers/invoice_helper_spec.rb
describe InvoiceHelper do
context "requesting the formatted subtotal" do
it "returns US dollars to two decimal places" do
invoice = double(Invoice, subtotal: 1012)
assign(:invoice, invoice)
expect(helper.subtotal_in_dollars).to eq "$10.12"
end
end
end
那么什么时候可以存根模型规格?嗯,这真的是一个判断调用,并且因人而异,代码库和代码库也各不相同。但是,仅仅因为app/models
中的某些内容并不意味着它必须是ActiveRecord模型。在这些情况下,在协作者上存储域API可能很好。
编辑:create
vs build
在上面的示例中,我使用了create(:invoice)
和invoice.line_items.create(cost: cost)
。但是,如果您担心数据库缓慢,您可能很容易使用build(:invoice)
和invoice.line_items.build(cost: cost)
。
请注意,我在这里使用create(:invoice)
和build(:invoice)
是指通用&#34;工厂&#34;,而不是对特定gem的引用。您只需在其位置使用Model.create
和Model.new
即可。此外,line_items.create
和line_items.build
由AR提供,与任何工厂宝石无关。