2
3

More than 3 years have passed since last update.

外部URLをCALLしているfaradayのrequestのretry機能のRSpecテストの書き方

Posted at

経緯:
サービス内において、Faradayを利用して外部のAPIをcallしている(salesforceとかそういうとこ)
だがリクエストに対してたまにTimeoutエラー(Faraday::TimeoutError)を返してエラーになる

そこでFaradayにretryオプションを指定して特定のエラーが起きた時のみretryをかけたい。
https://github.com/lostisland/faraday/blob/master/lib/faraday/request/retry.rb
faradayの中を調べるとこう書けとある。

    # @example Configure Retry middleware using intervals
    #   Faraday.new do |conn|
    #     conn.request(:retry, max: 2,
    #                          interval: 0.05,
    #                          interval_randomness: 0.5,
    #                          backoff_factor: 2,
    #                          exceptions: [CustomException, 'Timeout::Error'])
    #
    #     conn.adapter(:net_http) # NB: Last middleware must be the adapter
    #   end

sampleの通りに書いたらretryできた。

コードを書いたらテストも書かないといけない。
テストを書かないとt_wadaに こんな感じ で「圧」をかけられる

ちなみにリクエストをテストするにはWebMockのstub_requestがよい
https://github.com/bblimke/webmock

サンプルにある通り

stub_request(:post, "www.example.com").
  to_return(body: "abc")

こんな感じでテストが書ける。
テストの中でfaradayをMockせずとも外にリクエストを投げずにテストできる。
CIサーバの自動テストでも安心

今回やりたいタイムアウトの表現はこんな感じで書ける。

subject { (なんか post している処理) }
request = stub_request(:post, "www.example.com").
  to_timeout.times(2).then. # 2回タイムアウトして
  to_return(body: "abc") # その後正常に値が返る

expect(subject.status).to eq(200) # 正常にリクエストが返ってくる
expect(subject.body).to eq('abc')
expect(request).to have_been_requested.times(3)

エラーで終わる場合はこんな感じ

request = stub_request(:post, "www.example.com").
  to_timeout.times(3).then. # 3回タイムアウトして
  to_return(body: "abc") # もし次があればその後正常に値が返る

expect { subject }.to raise_error(Faraday::ConnectionFailed)
expect(request).to have_been_requested.times(3)

stub_requestはリクエストがパラメータなどの条件を満たさなかったり、3回呼ばれるはずが4回とか呼ばれたりするとエラーになるので安心
ただし回数が足らなくてもエラーにならないので、have_been_requested.times(回数)でチェックする必要がある

なお、実は上記テストは失敗する。
なぜかと言うと、webmockのstub_requestがto_timeoutで返すエラーは
Faraday::ConnectionFailedであり、faradayのretryされるエラーにはデフォルトではFaraday::ConnectionFailedは含まれない。

https://github.com/lostisland/faraday/blob/master/lib/faraday/adapter/net_http.rb#L64-L89
ここを見るとfaradayはtimeoutエラーを
・相手がrequestを受け取れなかったらFaraday::ConnectionFailed
・相手がresponseを返せずTimeout::Error, Errno::ETIMEDOUTになったらFaraday::TimeoutError
と区別している。

そしてfaradayのretryはデフォルトでは Faraday::ConnectionFailedをretry対象に含まない
対策は二つ。
テストをFaraday::TimeoutErrorに合わせる

request = stub_request(:post, "www.example.com").
  to_raise(Faraday::TimeoutError).times(3) # 3回タイムアウトする

expect { subject }.to raise_error(Faraday::TimeoutError)
expect(request).to have_been_requested.times(3)

もう一つはfaradayにFaraday::Error::ConnectionFailedでもretryしてくれるよう設定する

  Faraday.new do |conn|
    conn.request(:retry, max: 2,
                         interval: 0.05,
                         exceptions: Faraday::Request::Retry::DEFAULT_EXCEPTIONS + [Faraday::Error::ConnectionFailed]
                )
    ...
  end

以上終わり

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3