如何用RSpec测试Rails中自定义select查询的返回结果?
问题描述
在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
查询返回的对象可调用quantity_shipped_sum、price_sum、discount_sum这些动态生成的属性,但这些属性并非OrderItem模型的固有属性。
使用RSpec编写测试时,持续报错:OrderItem does not implement quantity_shipped_sum。尝试过两种Mock方式均失败,推测问题出在当前Mock的是OrderItem类,而where方法返回的是集合/关联对象。
解决方案
1. Mock完整查询调用链
where返回的是ActiveRecord::Relation对象,后续的select和first都基于这个关系对象执行,因此需要Mock整个调用链的返回值:
# 构建模拟的聚合结果对象 mock_sums = OpenStruct.new( quantity_shipped_sum: 10, price_sum: 100.0, discount_sum: 15.0, tenant_id: 1 ) # 依次Mock where -> select -> first 的调用 allow(OrderItem).to receive(:where).with(id: oiids).and_return( double("ActiveRecord::Relation", select: double(first: mock_sums)) )
2. 给OrderItem实例添加动态属性Stub
创建OrderItem实例后,手动为其添加动态属性的访问方法,再Mock查询链返回该实例:
sums = OrderItem.new sums.define_singleton_method(:quantity_shipped_sum) { 10 } sums.define_singleton_method(:price_sum) { 100.0 } sums.define_singleton_method(:discount_sum) { 15.0 } sums.tenant_id = 1 allow(OrderItem).to receive_message_chain(:where, :select, :first).and_return(sums)
3. 结合FactoryBot模拟(若项目已使用)
如果项目依赖FactoryBot,可以在工厂中定义带动态属性的实例:
factory :order_item_sum, class: OrderItem do transient do quantity_shipped_sum { 10 } price_sum { 100.0 } discount_sum { 15.0 } end after(:build) do |item, evaluator| item.define_singleton_method(:quantity_shipped_sum) { evaluator.quantity_shipped_sum } item.define_singleton_method(:price_sum) { evaluator.price_sum } item.define_singleton_method(:discount_sum) { evaluator.discount_sum } end end # 测试中调用 allow(OrderItem).to receive_message_chain(:where, :select, :first).and_return(build(:order_item_sum))
内容的提问来源于stack exchange,提问作者phil
相关产品推荐
相关产品推荐

