Search code examples
rubyrspecthor

How can I mock a Ruby "require" statement in RSpec?


I have a Ruby cli program that can optionally load a user-specified file via require. I would like to unit test this functionality via RSpec. The obvious thing to do is to mock the require and verify that it happened. Something like this:

context 'with the --require option' do
  let(:file) { "test_require.rb" }
  let(:args) { ["--require", "#{file}"] }
  it "loads the specified file"
    expect(...something...).to receive(:require).with(file).and_return(true)
    command.start(args)
  end
end

(That's just typed, not copy/pasted - the actual code would obscure the question.)

No matter what I try, I can't capture the require, even though it's occurring (it raises a LoadError, so I can see that). I've tried a variety of things, including the most obvious:

expect(Kernel).to receive(:require).with(file).and_return(true)

or even:

let(:kernel_class) { class_double('Kernel') }
kernel_class.as_stubbed_const
allow(Kernel).to receive(:require).and_call_original
allow(Kernel).to receive(:require).with(file).and_return(true)

but nothing seems to hook onto the require

Suggestions?


Solution

  • So require is defined by Kernel but Kernel is included in Object so when you call require inside this context it is not necessarily the Kernel module that is processing the statement.

    Update

    I am not sure if this exactly solves your issue but it does not suffer from the strange behavior exhibited below:

    file = 'non-existent-file'
    allow(self).to receive(:require).with(file).and_return(true)
    expect(self).to receive(:require).with(file)
    expect(require file).to eq(true)
    

    Working Example

    OLD Answer:

    This is incorrect and exists only for posterity due to the up-votes received. Some how works without the allow. Would love it if someone could explain why as I assumed it should raise instead. I believe the issue to be related to and_return where this is not part of the expectation. My guess is we are only testing that self received require, with_file, and that the and_return portion is just a message transmission (thus my updated answer)

    You can still stub this like so:

    file = 'non-existent-file.rb'
    allow_any_instance_of(Kernel).to receive(:require).with(file).and_return(true)
    expect(self).to receive(:require).with(file).and_return(true)
    require file
    

    Since I am unclear on your exact implementation since you have obfuscated it for the question I cannot solve your exact issue.