ruby-on-rails 如何使用Minitest测试begin-rescue块的重试逻辑?

0dxa2lsx  于 2023-05-13  发布在  Ruby
关注(0)|答案(1)|浏览(102)

假设有一个使用processA的服务,这是一个幂等进程,当调用它时,期望它找到或创建一个Payment记录并返回它。

class processA
  attr_reader :payment

  def initialize(operation_id)
    @payment = Payment.find_or_initialize_by(operation_id: operation_id)
  end

  def call
    update_payment_attributes
    payment.save!
  end

  private

  def update_payment_attributes
    # assign/update payment attributes
  end
end

假设processA是幂等的,processA会查找与给定operation_id匹配的现有记录,但如果没有找到,则会示例化一个具有所述属性的记录,保存并返回该记录。
如果没有找到与传递的operation_id匹配的Payment记录,并且开始创建新的Payment,则processB可能会发生竞争条件,这可能会创建一个具有相同operation_idPayment,其速度可能比processA完成任务的速度更快。
如果发生这种情况,当新的Payment保存在processA中时,由于某些模型约束阻止两个Payment记录具有相同的operation_id,因此会引发ActiveRecord::RecordNotUnique错误。

问题:

我在服务processAConsumer中有一些重试逻辑,所以在processA引发错误的情况下,它只是被第二次调用,这样processB创建的Payment就会被找到并返回。

class processAConsumer
  
  def call(operation_id)
    begin
      retries ||= 0
      payment = processA.new(operation_id).call
    rescue PG::UniqueViolation, ActiveRecord::RecordNotUnique
      retry if (retries += 1) < 3
    end
  end
end

使用Minitest,我如何测试这个重试逻辑?有没有办法将第一个processA#call存根,这样它就会引发错误并启动重试逻辑?
我尝试过不同的东西,看起来像这样,没有成功:

test "retry logic should succeed" do
    processA.any_instance.expects(:call) do
      1.times do
        raise PG::UniqueViolation, ActiveRecord::RecordNotUnique
      end
    end

    response = processAConsumer.call(123)

    assert response.success? # fails because the retry logic is not kicking in.
end
0ejtzxu1

0ejtzxu11#

对于任何想知道的人,这是我用来测试重试逻辑的:

test "should retry on PG::UniqueViolation exception" do
    payment = payments(:some_payment) # payment fixture

    processA.any_instance
      .stubs(:call)
      .raises(PG::UniqueViolation)
      .then.returns(payment)

    response = processAConsumer.call(123)

    assert response.success?
  end

这使得第一个processA#call返回PG::UniqueViolation错误。重试逻辑启动,第二次调用processA``#call时返回Payment的示例。

相关问题