17949


システムをrubyのバージョンをアップ（ruby 1.9.2p320⇒2.2.3）しました。Railsのバージョンは3.2.19
DBはMySQLを使用しています。  
テストを実行したところエラーが発生し以下のようなエラーが発生しました。

[code]

    Failures:
    
      1) …の場合は、 そのまま例外をraiseする。
         Failure/Error: lambda{Job.find_by_id(@conn, 0)}.should raise_error(ex)
           expected Unauthorized but nothing was raised
    
[/code]

プロダクトコードは以下のようでテストでmrqを実行したときに任意のエラーを発生させようとしてます。

[code]

    def self.find_by_id(conn, id)
      options = {…}
      response = Job.mrq(conn, options)
    
[/code]

テストコードです。

[code]

    before(:each) do
      Job.should_receive(:mrq).and_raise(ex)
    end
    context "…の場合は、" do
      let(:ex){SomeError.new("Unauthorized", 401)}
      it "そのまま例外をraiseする。" do
        lambda{Job.find_by_id(@conn, 0)}.should raise_error(ex)
      end
    end
    
[/code]

rspec (2.11.0)を使用してます。ruby 1.9.2では動作していました。


CC BY-SA 3.0
2015-10-22T02:31:54.777
17919
2015-10-23T04:11:42.627
2015-10-22T10:18:06.070
2599
10851
question
ruby-on-rails ruby rspec
RSpecでエラーを発生させたいのにnothing was raisedと出る


本当にRubyのバージョンアップがテスト失敗の原因なのでしょうか？  
テストコードも本番コードも変更していないのに、バージョンアップ後にテストが失敗するようになったのでしょうか？

とりあえず、コードを見たところ `mrq` にスタブを当てているのに、テストコードの中では `find_by_id` が呼ばれているように見えます。

正しくはこうなるのではないでしょうか？

[code]

    before(:each) do
      Job.should_receive(:mrq).and_raise(ex)
    end
    context "…の場合は、" do
      let(:ex){SomeError.new("Unauthorized", 401)}
      it "そのまま例外をraiseする。" do
        # find_by_id => mrq
        lambda{Job.mrq(@conn, 0)}.should raise_error(ex)
      end
    end
    
[/code]

## 追記

以下のように書くと結果はどうなりますか？

[code]

    context "…の場合は、" do
      let(:ex){SomeError.new("Unauthorized", 401)}
      it "そのまま例外をraiseする。" do
        Job.should_receive(:mrq).and_raise(ex)
        Job.find_by_id(@conn, 0)
      end
    end
    
[/code]

`:mrq`が呼ばれなかったという理由でFailするようであれば、そもそもfind_by_idでJob.mrqが呼ばれるようなフローになっていないのかもしれません。（意図しない条件分岐に入ってしまっている等）

また、find_by_idの実装を以下のように極端にシンプルに変えると確実にスタブが呼ばれてエラーが発生する、つまりテストがパスするはずです。これも試してみてください。

[code]

    def self.find_by_id(conn, id)
      Job.mrq(conn, nil)
    end
    
[/code]

このように、「エラーの原因の仮説を立てる」「仮説が正しいことを検証できるコードを書く」「結果を確認する」「期待した結果が得られない（＝仮説が間違っていた）場合は別の仮説を立てる」「コードを書いて実行する」・・・（以下繰り返し）、というような「当たり前のデバッグ手順」を踏んで原因を絞り込んでいくしかないかなーという気がします。  
また、デバッガを使ってステップ実行していくのも有効でしょう。

質問で書かれているコード自体は大きな問題がないように見えます。


CC BY-SA 3.0
2015-10-22T03:54:51.013
17926
2015-10-22T08:41:49.873
2015-10-22T08:41:49.873
85
85
17919
answer


ドキュメントに書いてあるように、`raise_error`マッチャ―の引数に渡すのは例外クラスです。例外クラスのインスタンスではありません。  
<https://www.relishapp.com/rspec/rspec-expectations/v/2-11/docs/built-in-
matchers/raise-error-matcher>

以下のように、`SomeError`クラスを渡すようにすると通ると思います。

[code]

    lambda{Job.find_by_id(@conn, 0)}.should raise_error(SomeError)
    
[/code]

より詳しく例外オブジェクトについて検証したいのであれば、ブロックを渡して検証するといいです。

[code]

    lambda{Job.find_by_id(@conn, 0)}.should raise_error(SomeError) {|e| e.should eq ex }
    
[/code]

<https://www.relishapp.com/rspec/rspec-expectations/v/2-11/docs/built-in-
matchers/raise-error-matcher#set-expectations-on-error-object-passed-to-block>

## なぜRubyのバージョンを上げて落ちるようになったか

内部の実装的には、`raise_error`マッチャーの引数に渡した値が`rescue`節に渡ります。  
<https://github.com/rspec/rspec-
expectations/blob/v2.11.0/lib/rspec/matchers/built_in/raise_error.rb#L23>

以下のように、`rescue`に例外クラスのインスタンスを渡すようなプログラムが、Ruby 1.9.2では実行出来るが、Ruby
2.2.3では実行できなくなったため、バージョンアップで落ちるようになったのではないでしょうか。

[code]

    class FooError < StandardError; end
    bar = FooError.new
    begin; raise bar; rescue bar => e; p e; end
    
[/code]


CC BY-SA 3.0
2015-10-22T09:05:56.167
17949
2015-10-23T04:11:42.627
2015-10-23T04:11:42.627
2599
2599
17919
answer