web-dev-qa-db-ja.com

RSpecを使用してメソッドをグローバルにスタブするにはどうすればよいですか?

Railsアプリケーションに取り組んでいます。メソッドをグローバルにスタブしようとしています。

私がやっていることは、RSpec構成内のbefore(:suite)ブロックで次のようにスタブ化することです。

_RSpec.configure do |config|
  config.before(:suite) do
    allow_any_instance_of(MyModel).to receive(:my_method).and_return(false)
  end
end
_

ただし、テストの開始は次のエラーで失敗します。

in `method_missing': undefined method `allow_any_instance_of' for #<RSpec::Core::ExampleGroup:0x00000008d6be08> (NoMethodError)

どんな手掛かり? RSpecを使用してメソッドをグローバルにスタブするにはどうすればよいですか?

P.

27
p.matsinopoulos

おそらくコンテキスト/初期化の問題です。 config.before(:each)でこれを行うと、問題が解決するはずです。

19

rspec-mocks README に記載されているように、各例の後でスタブがクリアされるため、before(:suite)でメソッドをスタブしないでください。

before(:each)ではなくbefore(:all)を使用してください

before(:all)のスタブはサポートされていません。その理由は、各例の後ですべてのスタブとモックが消去されるため、before(:all)に設定されているスタブは、そのグループで偶然実行される最初の例では機能しますが、他の例では機能しないためです。

before(:all)の代わりにbefore(:each)を使用してください。

_allow_any_instance_of_がbefore(:suite)ブロックでは使用できないが、before(:each)ブロックでは使用できるのはそのためだと思います。

それでもメソッドが見つからない場合は、_:should_構文のみを許可するようにrspec-mocksを構成した可能性があります。 _allow_any_instance_of_はRSpec 2.14で導入され、メッセージを期待するためのすべての新しい_:expect_構文を備えています。

_RSpec::Mocks.configuration.syntax_の値を調べて、この構文が有効になっていることを確認してください。これは、rspec-mocksで使用可能な構文の配列です。使用可能な構文は_:expect_および_:should_です。

_RSpec.configure do |config|
  config.mock_with :rspec do |mocks|
    mocks.syntax = [:expect, :should]
  end
end
_

適切に構成すると、_allow_any_instance_of_を使用できるようになります。

15
cbliard

最近、before(:all)またはbefore(:context)ブロックで何かをスタブする必要がある場合に遭遇し、ここでの解決策が私のユースケースでは機能しないことがわかりました。

before()&after()フック のRSpecドキュメントはサポートされていないと言っています:

beforeフックとafterフックは、それらを実行する必要のあるサンプルグループ、またはグローバルRSpec.configureブロックで直接定義できます。

警告:インスタンス変数の設定はbefore(:suite)ではサポートされていません。

警告:モックはbefore(:example)でのみサポートされています

注::exampleおよび:contextスコープは、それぞれ:eachおよび:allとしても使用できます。どちらでもお使いください。

問題

私は バイナリファイル形式を書き込むための宝石 を作成しました。これは、バイナリヘッダー内のUNIX Epochタイムスタンプに含まれていました。 RSpecテストを記述して、出力ファイルのヘッダーが正しいかどうかを確認し、それをテストフィクスチャのバイナリ参照ファイルと比較したいと思いました。高速なテストを作成するために、すべてのサンプルグループブロックが実行される前に、ファイルを一度書き出す必要がありました。タイムスタンプを参照ファイルと照合するために、Time.now()が定数値を返すように強制する必要がありました。これにより、目的の値を返すために_Time.now_をスタブ化しようとする道が進みました。

ただし、_rspec/mocks_はbefore(:all)またはbefore(:context)ブロック内でのスタブをサポートしていないため、機能しませんでした。ファイルbefore(:each)を書き込むと、他の奇妙な問題が発生しました。

幸いにも、私は偶然出会いました rspec-mocksの問題#24 解決策がありました!

解決

2014年1月9日以降( rspec-mocks PR#519 )RSpecには、これを回避するメソッドが含まれています。

_RSpec::Mocks.with_temporary_scope_

_require 'spec_helper'
require 'rspec/mocks'

describe 'LZOP::File' do
  before(:all) {
    @expected_lzop_magic = [ 0x89, 0x4c, 0x5a, 0x4f, 0x00, 0x0d, 0x0a, 0x1a, 0x0a ]
    @uncompressed_file_data = "Hello World\n" * 100
    @filename = 'lzoptest.lzo'
    @test_fixture_path = File.join(File.dirname(__FILE__), '..', 'fixtures', @filename + '.3')
    @lzop_test_fixture_file_data = File.open( @test_fixture_path, 'rb').read
    @tmp_filename = File.basename(@filename)
    @tmp_file_path = File.join( '', 'tmp', @tmp_filename)

    # Stub calls to Time.now() with our fake mtime value so the mtime_low test against our test fixture works
    # This is the mtime for when the original uncompressed test fixture file was created
    @time_now = Time.at(0x544abd86)
  }

  context 'when given a filename, no options and writing uncompressed test data' do

    describe 'the output binary file' do
      before(:all) {
        RSpec::Mocks.with_temporary_scope do
          allow(Time).to receive(:now).and_return(@time_now)
          # puts "TIME IS: #{Time.now}"
          # puts "TIME IS: #{Time.now.to_i}"
          my_test_file = LZOP::File.new( @tmp_file_path )
          my_test_file.write( @uncompressed_file_data )
          @test_file_data = File.open( @tmp_file_path, 'rb').read
        end
      }

      it 'has the correct magic bits' do
        expect( @test_file_data[0..8].unpack('C*') ).to eq @expected_lzop_magic
      end

      ## [...SNIP...] (Other example blocks here)
      it 'has the original file mtime in LZO file header' do
        # puts "time_now= #{@time_now}"

        if @test_file_data[17..21].unpack('L>').first & LZOP::F_H_FILTER == 0
          mtime_low_start_byte=25
          mtime_low_end_byte=28
          mtime_high_start_byte=29
          mtime_high_end_byte=32
        else
          mtime_low_start_byte=29
          mtime_low_end_byte=32
          mtime_high_start_byte=33
          mtime_high_end_byte=36
        end
        # puts "start_byte: #{start_byte}"
        # puts "end_byte: #{end_byte}"
        # puts "mtime_low: #{@test_file_data[start_byte..end_byte].unpack('L>').first.to_s(16)}"
        # puts "test mtime: #{@lzop_test_fixture_file_data[start_byte..end_byte].unpack('L>').first.to_s(16)}"

        mtime_low = @test_file_data[mtime_low_start_byte..mtime_low_end_byte].unpack('L>').first
        mtime_high = @test_file_data[mtime_high_start_byte..mtime_high_end_byte].unpack('L>').first
        # The testing timestamp has no high bits, so this test should pass:
        expect(mtime_low).to eq @time_now.to_i
        expect(mtime_high).to eq 0

        expect(mtime_low).to eq @lzop_test_fixture_file_data[mtime_low_start_byte..mtime_low_end_byte].unpack('L>').first
        expect(mtime_high).to eq @lzop_test_fixture_file_data[mtime_high_start_byte..mtime_high_end_byte].unpack('L>').first

        mtime_fixed = ( mtime_high << 16 << 16 ) | mtime_low

        # puts "mtime_fixed: #{mtime_fixed}"
        # puts "mtime_fixed: #{mtime_fixed.to_s(16)}"

        expect(mtime_fixed).to eq @time_now.to_i

      end
    end
  end
end
_
8
TrinitronX

特定のメソッドがテストスイート全体に対して特定の動作をするようにしたい場合は、RSpecのスタブを処理する理由すらありません。代わりに、メソッドを単純に(再)定義して、テスト環境での動作を指定できます。

class MyModel
  def my_method
    false
  end
end

これはspec/spec_helper.rbまたは同様のファイル。

5
Myron Marston

RSpecのどのバージョンを使用していますか?私は信じている allow_any_instance_ofはRSpec 2.14で導入されました。以前のバージョンでは、以下を使用できます。

MyModel.any_instance.stub(:my_method).and_return(false)
2
Erik Ostrom

以下を使用して、クラス 'Xyz'のメソッド 'do_this'をスタブ化できます。

allow_any_instance_of(Xyz).to receive(:do_this).and_return(:this_is_your_stubbed_output)

これにより、この関数が呼び出された場所から-:: this_is_your_stubbed_outputに出力がスタブされます。

上記のコードをbefore(:each)ブロックで使用して、仕様のすべての例にこれを適用できます。

1