我应该如何使用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 # (NoMethodError)

任何线索? 我应该如何使用RSpec全局存根方法?

P.

它可能是一个上下文/初始化问题。 在config.before(:each)执行它可以解决您的问题。

不要在before(:suite)存根方法,因为在每个示例之后清除存根,如rspec-mocks README中所述 :

使用before(:each) ,而不是before(:all)

不支持before(:all)中的存根。 原因是在每个示例之后都清除了所有存根和模拟,因此before(:all)设置的任何存根将在第一个在该组中运行的示例中工作,但不会在其他任何示例中运行。

而不是before(:all) ,使用before(:each)

我认为这就是为什么allow_any_instance_ofbefore(:suite)块中不可用,但在before(:each)块中可用。

如果方法仍然缺失,可能您将rspec-mocks配置为仅允许:should syntax。 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

我最近遇到了一个案例,我需要在before(:all)before(:context)块中存根,并发现这里的解决方案对我的用例不起作用。

关于before()和after()钩子的 RSpec文档说它不受支持:

钩子之前和之后可以直接在它们应该运行的示例组中定义,或者在全局RSpec.configure块中定义。

警告:before(:suite)中不支持设置实例变量。

警告:仅在之前支持模拟(例如)。

注意::example和:context作用域也分别可用作:each和:all。 使用您喜欢的任何一种。

问题

我正在制作一个用于编写二进制文件格式的gem,该格式包含在其二进制头文件中的unix epoch时间戳。 我想编写RSpec测试以检查输出文件头的正确性,并将其与测试夹具二进制参考文件进行比较。 为了创建快速测试,我需要在所有示例组块运行之前将文件写出一次。 为了检查参考文件的时间戳,我需要强制Time.now()返回一个常量值。 这导致我试图存根Time.now返回我的目标值。

但是,由于rspec/mocks不支持在before(:all)before(:context)块中的存根,因此它不起作用。 before(:each)写入文件会导致其他奇怪的问题。

幸运的是,我偶然发现了具有解决方案的rspec-mocks的#240号问题 !

自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 

如果您希望某个特定方法对整个测试套件采用某种方式,那么就没有理由处理RSpec的存根。 相反,您可以简单地(重新)定义方法,以便在测试环境中按照您的方式运行:

 class MyModel def my_method false end end 

这可以在spec/spec_helper.rb或类似文件中找到。

您使用的是什么版本的RSpec? 我相信在RSpec 2.14中引入了allow_any_instance_of 。 对于早期版本,您可以使用:

 MyModel.any_instance.stub(:my_method).and_return(false) 

您可以使用以下命令来存储类’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)块中使用上面的代码,以使其适用于您的所有规范示例。