gpt4 book ai didi

ruby-on-rails - 如何在 rspec 中测试 ActiveRecord::Relation 对象上的方法?

转载 作者:行者123 更新时间:2023-12-04 03:20:53 25 4
gpt4 key购买 nike

如何测试仅适用于 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返回一个未定义 sum 的常规数组与 ActiveRecord::Relation 对象一样。

任何帮助是极大的赞赏。

最佳答案

我不确定您使用的是哪个 Rails,因此我将在此示例中使用 Rails 4.0.x;这个原则仍然适用于 Rails 3.x。

TL;DR:你不想走这条路。

  • 考虑不要 stub 模型规范
  • 考虑添加特定领域的 API

  • 您正在迅速走上过度 mock / stub 的道路。我一直在这条路上,它不会带来乐趣。部分原因归结为违反 Law of Demeter .其中一部分归结为使用 Rails API,而不是创建自己的域 API。

    当您从 ActiveRecord 请求关系集合时模型它不返回 Array如你所知。在 Rails 4.0.x 中,带有 has_many关联,返回的类是: ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Model .

    问题 #1: stub 错误的返回值

    这里你的返回类型是 Array .而实际的返回类型是 ActiveRecord_Associations_CollectionProxy_Model .在 stub /模拟土地中,这不一定是坏事。但是,如果您打算对 stub 返回的对象使用其他调用,则它们需要匹配相同的 API 协定。否则,你不会 stub 相同的行为。

    在这种情况下, sum AR关联代理上定义的方法在运行时实际执行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

    如您所见,它对元素求和,而不是请求属性的总和。

    问题#2:断言你的 stub 对象

    您遇到的另一个主要问题是您试图指定您的 stub 返回您 stub 的内容。这没有意义。 stub 的目的是返回一个固定的答案。这不是断言它的行为方式。

    您写的内容与以下内容没有根本不同:
    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])

    除非这应该是一个健全性检查,否则它不会为您的规范增加任何实际值(value)。

    建议

    我不确定你在这里写的是什么类型的规范。如果这是一个更传统的单元测试或验收测试,那么我可能不会 stub 任何东西。有时访问数据库不一定有任何问题,尤其是当您正在测试的是您如何与之交互时;这就是你在这里所做的。

    您可以做的另一件事是开始使用它来创建您自己的特定域模型 API。所有这一切实际上意味着在对您的域有意义的对象上定义接口(interface),这些接口(interface)可能由数据库或其他资源支持,也可能不支持。

    例如,将您的 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在代码的其他部分中,如果需要,您可以轻松地 stub :
    # 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

    那么什么时候可以 stub 模型规范?嗯,这确实是一个判断电话,并且会因人而异,也会因代码库而异。然而,仅仅因为 app/models 中有东西并不意味着它必须是 ActiveRecord 模型。在这些情况下,在协作者上 stub 域 API 可能很好。

    编辑:create对比 build

    在上面的示例中,我使用了 create(:invoice)invoice.line_items.create(cost: cost) .但是,如果您担心 DB 速度慢,您可能也可以轻松地使用 build(:invoice)invoice.line_items.build(cost: cost) .

    请注意,我使用 create(:invoice)build(:invoice)这里是指通用“工厂”,而不是对特定 gem 的引用。您可以简单地使用 Model.createModel.new在他们的位置。此外, line_items.createline_items.build由 AR 提供,与任何工厂 gem 无关。

    关于ruby-on-rails - 如何在 rspec 中测试 ActiveRecord::Relation 对象上的方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23536436/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com