2

I'm writing a client for an API that rescue from Faraday::ConnectionFailed and Faraday::TimeoutError to retry the same method MAX_RETRIES times.

This is the main method involved:

def benchmark_request(path)
  retries ||= 0
  request_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)

  response = yield

  total_request_seconds = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - request_start_time)
  Rails.logger.info "client request took (#{total_request_seconds}s): #{ENV['API_PATH_PREFIX']}#{path}"

  response
rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
  retries += 1
  retry if retries <= MAX_RETRIES
end

the method calling that is:

 def get(path, params = {})
   benchmark_request(path) { token.get("#{ENV['API_PATH_PREFIX']}#{path}", params) }
 end

token.get comes from the oauth2 gem which is using Faraday

Here's the fun bit. I wrote 2 specs, 1 for each exception I want to handle.

context 'when the endpoint raises a ConnectionFailed' do
  let(:token_expires_at) { 1.hour.from_now.to_i }
  let(:response_body) { '' }
  let(:response_status) { 200 }

  before do
    allow(token).to receive(:get).and_raise(Faraday::ConnectionFailed)
    described_class.get(api_endpoint)
  end

  it 'is called MAX_RETRIES times' do
    expect(token).to have_received(:get).exactly(3).times
  end
end

context 'when the endpoint raises a TimeoutError' do
  let(:token_expires_at) { 1.hour.from_now.to_i }
  let(:response_body) { '' }
  let(:response_status) { 200 }

  before do
    allow(token).to receive(:get).and_raise(Faraday::TimeoutError)
    described_class.get(api_endpoint)
  end

  it 'is called MAX_RETRIES times' do
    expect(token).to have_received(:get).exactly(3).times
  end
end

The test testing ConnectionFailed fails, the test testing TimeoutError is green. The exception raised is:

1) Client::Base.get when the endpoint raises a ConnectionFailed is called MAX_RETRIES times
 Failure/Error: token.get(path, params)

 ArgumentError:
   wrong number of arguments (given 0, expected 1..2)
 # /home/ngw/.rvm/gems/ruby-2.6.2/gems/faraday-0.15.4/lib/faraday/error.rb:7:in `initialize'
 # ./app/lib/client/base.rb:13:in `get'
 # ./spec/lib/client/base_spec.rb:111:in `block (4 levels) in <top (required)>'

Which apparently is about how the Exception is initialized.

Does anybody have any idea?

ngw
  • 1,222
  • 1
  • 14
  • 34

2 Answers2

0
before do
   allow(token).to receive(:get).and_raise(Faraday::TimeoutError, 'execution expired')
  described_class.get(api_endpoint)
end

I solved this by passing a second argument to the and_raise method. I think it's because Faraday has slightly different exception classes.

MikeRogers0
  • 721
  • 6
  • 14
0

It happens because Farady::ConnectionFailed inherits from Faraday::Error class which in its def initialize requires at least one argument. Check the source here (Error) and here (ConnectionFailed).

TimeoutError works because it has default value set for exc to "timeout" here.

It was difficult to spot on a first glance and I totally understand you that you were lost as well.

As @MikeRogers0 mentioned in his solution, you have to use second argument in and_raise.

I hope you will find this answer helpful.

Bob
  • 106
  • 7