Rspec期望改变不返回正确的结果



我不明白为什么rspec期望改变匹配器不工作,即使我复制了逻辑,我确信代码是正确的。

我有:

class SubscriptionItem < ApplicationRecord
after_commit :create_customer_quotas, on: :create
def create_customer_quotas
CustomerQuotaService.create_from_subscription_item! self
end
def self.create_from_subscription_item!(subscription_item)
customer = subscription_item.customer
subscription_item.quotas.each do |quota|
CustomerQuota.find_or_create_by! quota:, subscription_item:, customer:
end
end

在我的测试中,我有这个:

expect { subscription.save! }.to change(CustomerQuota, :count).by(subscription.quotas.count)

,我得到这个失败:

expected `CustomerQuota.count` to have changed by 1, but was changed by 0

但是,当我复制期望在测试中更改逻辑时,像这样:

count_before = CustomerQuota.count
subscription.save!
count_after = CustomerQuota.count
puts "before=#{count_before} && after=#{count_after}"

输出为:before=0 && after=1

所以它显然是有效的。为什么期望的改变没有像预期的那样起作用?

我怀疑这是由于after_commit回调,因为如果我们使用事务性fixture,它不会在rspec中触发。

我们需要通过ar_object.run_callbacks(:after_commit)手动触发after_commit回调

expect { subscription.save!; subscription.run_callbacks(:after_commit) }.to change(CustomerQuota, :count).by(subscription.quotas.count)

或者你可以使用test_after_commit gem,它会在事务提交后立即连接after_commit回调。绑定这个gem会自动运行after_commit回调,而不需要任何手动触发。在rails 5.0+

上不再需要这个了。

如果你不想(或者你不能)修改代码,就用Sampat Badhe的答案(使用test_after_commit或升级到rails>= 5.0.0),不要再读这篇文章了:这个答案是关于软件架构的,而不是关于解决你的rspec问题的。

但是我想知道的是....真的需要在after_commit回调中完成吗?如果CustomerQuotaService.create_from_subscription_item! self失败了怎么办?您已经将SubscriptionItem提交到数据库。

您可以将回调更改为before_create :build_customer_quotas在你的CustomerQuotaService呼叫find_or_initializebuild而不是find_or_create

这将确保所有内容都在一个事务中,并将正确保存或回滚。

更新:

一些可能有效的代码:

class SubscriptionItem < ApplicationRecord
before_create :build_customer_quotas
def build_customer_quotas
quotas.each do |quota|
customer_quotas.build quota: quota, customer: customer
end
end
end

当rails保存subscription_item时,它将自动保存customer_quotas。不需要额外的save调用。

指出:

  • build_customer_quotas被称为before_create时,您不需要检查现有customer_quotas的存在性
  • 你没有提到你如何在你的服务中创建你要迭代的quotas。您可以使用类似的技术来构建它们并将它们链接到内存中的subcription_item对象中。

最新更新