我应该如何使用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_of
在before(: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)块中使用上面的代码,以使其适用于您的所有规范示例。