15

I have a function, which accepts a block, opens a file, yields and returns:

def start &block
    .....do some stuff
    File.open("filename", "w") do |f|
        f.write("something")
        ....do some more stuff
        yield
    end
end

I am trying to write a test for it using rspec. How do I stub File.open so that it passed an object f (supplied by me) to the block instead of trying to open an actual file? Something like:

it "should test something" do
    myobject = double("File", {'write' => true})
    File.should_receive(:open).with(&blk) do |myobject|
        f.should_receive(:write)
        blk.should_receive(:yield) (or somethig like that)
    end
end
constantine1
  • 427
  • 1
  • 5
  • 12
  • 1
    It's hard to test this because what you want to test is internal to the method. How does your file interact with anything outside the method? From your example, you don't use the file outside the method at all, and you don't yield it either. If something outside this function needs the file, then maybe you should consider passing the file object in to the method (dependency injection). Then you can mock the file no problem. If you do need to test something about that file, maybe you should consider breaking it out into a separate method? – Raj Feb 27 '13 at 01:03
  • Thanks...did something like that, broken into multiple methods, stubbed few functions like File.stub(open).and_yield(my_double), etc. – constantine1 Mar 05 '13 at 09:07

2 Answers2

7

I think what you're looking for are yield matchers, i.e:

it "should test something" do
  # just an example
  expect { |b| my_object.start(&b) }.to yield_with_no_args
end
Amir
  • 1,882
  • 17
  • 22
  • How to I stub File.open to supply a mock object inside the function (start) being tested? – constantine1 Feb 19 '13 at 11:34
  • Let me restate - how do I stub File.open to supply a mock object to its block, so that I can test something like f.should_receive(:write).with("what should be written to file") – constantine1 Feb 19 '13 at 12:04
  • I missed your comment and only today clicked on "responses". Basically File.open sends the block *self* (try running `puts f.inspect` from inside the block), so mocking `File.should_receive(:write)` should work. Sorry for the late reply but hopefully this would be useful to someone :) – Amir Jun 19 '13 at 19:16
  • 1
    I did by mocking _File.should_receive(:open).and_yield(my_mock_object)_ and adding checks on _my_mock_object.should_receive(:write)_, etc... – constantine1 Jun 20 '13 at 08:01
  • @constantine1 Your solution in the comment was exactly was I was looking to do. Thanks! – Fralcon Sep 10 '14 at 02:43
1

Your other choice is to stub :open with a new object of File, as such:

file = File.new
allow(File).to receive(:open) { file }
file.each { |section| expect(section).to receive(:write) }
# run your method
La-comadreja
  • 5,627
  • 11
  • 36
  • 64
  • Be careful with this. I've found in my experience that if you use RSpec stubs / mocks on basic system classes, like `File`, it tends to lead to a lot of problems. For example, if you use the VCR gem in your tests, it will break in spectacular fashion if you allow(File).to receive pretty much anything. – Jazz May 15 '15 at 17:19