ruby-on-rails 使用rspec测试rails 'select'

lrpiutwd  于 2023-08-08  发布在  Ruby
关注(0)|答案(1)|浏览(138)

我们在Rails应用程序中执行以下操作:

sums = OrderItem.where(id: oiids).select("sum(quantity_shipped) as quantity_shipped_sum, sum(price) as price_sum, sum(discount) as discount_sum, tenant_id").first

字符串
在这个select之后,我们可以在返回的Association上执行sums.quantity_shipped_sum
如何在Rspec中用这个来编写测试?我所做的一切都告诉我OrderItem does not implement quantity_shipped_sum或类似的错误。需要明确的是,quantity_shipped_sum不是OrderItem上的方法/属性,它只是由这个select创建的。
我试过了

allow(OrderItem).to receive(:quantity_shipped_sum).and_return(2)


和/或

allow(OrderItem).to receive(:first).and_return(instance_double(OrderItem, quantity_shipped_sum: 2, price_sum: 10.0, discount_sum: 1.0, tenant_id: 1))


对于更多的上下文,这里是一些更多的spec文件:

allow(OrderItem).to receive(:where).and_return(OrderItem)
allow(OrderItem).to receive(:select).and_return(OrderItem)
allow(OrderItem).to receive(:distinct).and_return(OrderItem)
allow(OrderItem).to receive(:pluck).and_return([order_item1.id, order_item2.id])
allow(OrderItem).to receive(:first).and_return(instance_double(OrderItem, quantity_shipped_sum: 2, price_sum: 10.0, discount_sum: 1.0, tenant_id: 1))


我想我知道为什么这些失败了--因为它们在类上,而where返回一个集合/关联。

7nbnzgx9

7nbnzgx91#

有几个问题。让我们从这里开始:
你的代码是这样做的:

OrderItem.where(...).select(...).first

字符串
此代码对order_items表执行SQL SELECT语句,返回包含OrderItem示例的ActiveRecord::Relation示例,然后获取Relation中的第一个OrderItem示例。
您的测试存根正在执行以下操作:

allow(OrderItem).to receive(:where).and_return(OrderItem)


这将正确地存根where方法。但是,返回OrderItem类,而不是ActiveRecord::Relation的示例。所以,现在,当代码执行时,select将被调用到OrderItem类。这看起来是可行的,因为OrderItem继承了ActiveRecord::Base,而ActiveRecord::Base确实有一个select方法。但严格来说这是不正确的。
所有allow(OrderItem)语句都是如此。
真实的的问题在这里:

allow(OrderItem)
  .to receive(:first)
  .and_return(
    instance_double(
      OrderItem, 
      quantity_shipped_sum: 2, 
      price_sum: 10.0, 
      discount_sum: 1.0, 
      tenant_id: 1
    )
  )


在代码中,first是在ActiveRecord::Relation的示例上调用的。在allow语句指定的OrderItem类上不调用它。RSpec不会抛出错误,因为OrderItem继承自ActiveRecord::Base,而ActiveRecord::Base恰好包含first方法(如上所述)。但是,OrderItem没有名为quantity_shipped_sumprice_sumdiscount_sum的方法。RSpec的instance_double方法只允许您使用实际出现在类定义中的存根方法。
我觉得你其实想要这样的东西:

let(:order_item_with_sums) do
  instance_double(
    "order item", 
    quantity_shipped_sum: 2, 
    price_sum: 10.0, 
    discount_sum: 1.0, 
    tenant_id: 1
  )
end 

it "makes the correct calls" do
  expect(OrderItem)
    .to receive_message_chain(:where, :select, :first)

  result = # call the method that does the work here...
end


请注意,instance_double不使用OrderItem类。这将启用RSpec的特性,该特性验证类中是否存在存根方法。通过使用字符串"order item",可以禁用该功能。
同样需要注意的是,这种语法不允许您验证传入whereselect的任何参数。你得做不同的测试。
最后,为了避免不可避免的评论,你不应该使用receive_message_chain,我会说,一般的指导是,receive_message_chain被认为是一种气味,因为它允许你相当容易地违反Law of Demeter。这是ActiveRecord。ActiveRecord使用fluent interface将方法链接在一起。这些方法链不被认为违反了得墨忒耳定律。这是框架的一个特点。

相关问题