Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you stub a file.read that happens inside a File.open block?

Tags:

ruby

rspec

How do I stub a file.read call so that it returns what I want it to? The following does not work:

def write_something
  File.open('file.txt') do |f|
    return contents = f.read
  end
end
# rspec
describe 'stub .read' do
  it 'should work' do
    File.stub(:read) { 'stubbed read' }
    write_something.should == 'stubbed read'
  end
end

It looks like the stub is being applied to the File class and not the file instance inside my block. So File.read returns stubbed read as expected. But when I run my spec it fails.

like image 668
Brand Avatar asked Sep 10 '12 01:09

Brand


2 Answers

I should note that File.open is just one part of Ruby’s very large I/O API, and so your test is likely to be very strongly coupled to your implementation, and unlikely to survive much refactoring. Further, one must be careful with “global” mocking (i.e. of a constant or all instances) as it can unintentionally mock usages elsewhere, causing confusing errors and failures.

Instead of mocking, consider either creating an actual file on disk (using Tempfile) or using a broader I/O mocking library (e.g. FakeFS).

If you still wish to use mocking you can somewhat safely stub File.open to yield a double (and only when called with the correct argument):

file = instance_double(File, read: 'stubbed read')
allow(File).to receive(:open).and_call_original
allow(File).to receive(:open).with('file.txt') { |&block| block.call(file) }

or, somewhat dangerously, stub all instances:

allow_any_instance_of(File).to receive(:read).and_return('stubbed read')
like image 178
Andrew Marshall Avatar answered Sep 21 '22 12:09

Andrew Marshall


The main point is to make File.open to return an object that will respond to read with the content you want, here's the code:

    it "how to mock ruby File.open with rspec 3.4" do
      filename = 'somefile.txt'
      content = "this would be the content of the file"
      # this is how to mock File.open:
      allow(File).to receive(:open).with(filename, 'r').and_yield( StringIO.new(content) )
      # you can have more then one of this allow

      # simple test to see that StringIO responds to read()
      expect(StringIO.new(content).read).to eq(content)

      result = ""
      File.open('somefile.txt', 'r') { |f| result = f.read }
      expect(result).to eq(content)
    end
like image 32
Eduardo Santana Avatar answered Sep 18 '22 12:09

Eduardo Santana