views:

63

answers:

2

Below you can see that I'm calling File.open only once but rspec is telling me it received it 3 times.

def self.import_file(filename)
  current_file = filename.split('/').last
  destroy_all(["filename = ?",current_file])

  unpack_format = "A#{INPUT_FILE_FORMAT.map{|element| element[1]}.join("A")}"
  debugger
  File.open(filename, 'r').each do |line|
    hash_created = create_hash(line, unpack_format).merge({:filename=>current_file})
    create(hash_created)
  end
end  

it "should delete previous records with the same filename" do
  Payrec.should_receive(:destroy_all).with(["filename = ?", "testfile.txt"])
  File.should_receive(:open).and_return([@file_line])
  Payrec.import_file "testfile.txt"
end

The output is

<File (class)> expected :open with (any args) once, but received it 3 times
+2  A: 

Everybody and his dog calls File.open. I could imagine a ton of reasons why it would called: RSpec reading its config file, Rails reading its config file(s), Cucumber reading its config file, the debugger creating a temporary file, something else creating a temporary file and so on.

You should check who is calling File.open, where that call happens, what the arguments are and why that is happening.

But, this is something you will just have to deal with, when setting expectations on core methods.

Imagine, for example, you are running your specs on Rubinius. In Rubinius, the compiler is written in Ruby. It doesn't currently cache its compiled results, but it is certainly conceivable that it could cache them, and then it would naturally use File.open. Bam! Now your specs randomly break depending on whether you hit the JIT threshold or not.

Or, even worse: all of Rubinius uses arrays and symbols extensively to implement just about everything. Try setting some expectations on those!

Jörg W Mittag
One solution to this testing problem is to extract the `File.open` call into a separate method inside your own code and simply assert that that method is called.
James A. Rosen
+1  A: 

It's far from perfect, but as a quick hack for finding out what these File.open calls are actually doing, monkey patch this in at the top of your main script:

class File
  class << self
   alias_method :old_open, :open
    def open(*args, &block)
      p args
      block ? block.call(old_open(*args)) : old_open(*args)
    end
  end
end

Whenever File.open is called, the arguments will be put to screen.

Peter Cooper
cool, thanks peter. Hadn't thought of that.
btelles