1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[RequestSpec] APIの例外発生時のrenderする結果をテストしたい

Last updated at Posted at 2021-06-28

経緯

こちら側でAPI作ってたのをテストしたかった感じです。
今回は他の例外もキャッチするパターンも用意してました。
ただ、どれにも該当しない500エラー(InternalServerError)の発生方法に躓いて悩んでいました。
↓みたいな

rescue 他エラークラス => e
  render :json => { 'res': 'ng' }, status: 400
rescue => e # <= こいつ
  Rails.logger.debug(e.backtrace.join("\n"))
  render :json => { 'res': 'ng' }, status: 500

※他にもrescueパターンや共通化とかしてた部分とかは省略。

結論

対象のController#Methodをスタブ化すれば良かっただけでした。
(エラー箇所は変えて大丈夫かと。今回は500が欲しかったのでStandardErrorにしました)
※非推奨の部分は ちょっと調べてみた を見てください

allow_any_instance_of(HogeController).to receive(:method).and_raise(StandardError.new(nil))

# 非推奨なのでヤメました → HogeController.any_instance.stub(:method).and_raise(StandardError.new(nil))

e.g.

renderされるJSON

# 成功(200)
{ 'res': 'ok' }

# 失敗(500)
{ 'res': 'ng' }

controller

class Api::V1::HogeController < Api::V1::BaseController
  def test
    .
    .
    render :json => { 'res': 'ok' }, status: 200
  rescue => e
    render :json => { 'res': 'ng' }, status: 500
  end
end

request spec

require 'rails_helper'

describe Api::V1::HogeController, type: :request do
  subject { post '/api/v1/hoge/test' }

  it 'ok' do
    subject

    expect(response.status).to eq 200
    expect(response.body).to eq { 'res': 'ok' }
  end

  it 'ng' do
    allow_any_instance_of(Api::V1::HogeController,).to receive(:method).and_raise(StandardError.new(nil))    
    subject

    expect(response.status).to eq 500
    expect(response.body).to eq { 'res': 'ng' }
  end
end

参照

GitLabのgoogle_api/authorizations_controller_spec.rb から パクってきた ヒント得ました。
あと、Controllerのスタブ化で少し悩んだので こちらの方のブログ も参考にしました。

ありがとうございました。

ちょっと調べてみた

同じように悩んでる人いました。
https://cloud6.net/so/ruby-on-rails/3142894

ただ調べていくとどうも any_instance.stub は非推奨みたいです。
その代わりに allow_any_instance_of を使うことに。

ただ、 どうやら allow_any_instance_of も非推奨みたいです...
-> https://github.com/rspec/rspec-mocks#settings-mocks-or-stubs-on-any-instance-of-a-class

・The rspec-mocks API is designed for individual object instances, but this feature operates on entire classes of objects. As a result there are some semantically confusing edge cases. For example in expect_any_instance_of(Widget).to receive(:name).twice it isn't clear whether each specific instance is expected to receive name twice, or if two receives total are expected. (It's the former.)
・Using this feature is often a design smell. It may be that your test is trying to do too much or that the object under test is too complex.
・It is the most complicated feature of rspec-mocks, and has historically received the most bug reports. (None of the core team actively use it, which doesn't help.)

範囲が広いしテストが複雑化してるからテストケースの根本的な問題を直せ、的な?
バグissueも歴史的に多くあるらしい、のであまり使わない方が良いとのこと。

そこで allow_any_instance_of を使わない方向で調査してみました。
instance_doubleallow で対応されてる方が多かったです。
確かにController#Method はインスタンスメソッドだと考え下記のように書きました。

  hoge_mock = instance_double(Api::V1::HogeController)
  allow(Api::V1::HogeController).to receive(:new).and_return(hoge_mock)
  allow(hoge_mock).to receive(:test).and_raise(StandardError.new(nil))
  post '/api/v1/hoge/test'

→ ダメでした。
ちょっと端折りますが↓のようなエラーです。

#<InstanceDouble(Api::V1::HogeController) (anonymous)> received unexpected message :dispatch with ("test", #...)

以下独り言です。

憶測ですけど、 :new:test だけスタブ化したところで、Railsの動き的にController#Methodが呼ばれるまでに処理される内容(他の呼び出されるメソッド)を用意してなくてダメ?な気がします。

, but this feature operates on entire classes of objects.

と、 rspec-mocks のREADMEに書いてある通り、 「この機能はオブジェクトのクラス全体に作用します」 とのこと。
なので動いていたのかな、とか考えてたりします。

この辺りまだ調べきれていないので分かる人いたら教えていただけますと嬉しいです。

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?