问题
I'm attempting to stub File.open in order to test a method I have that reads a CSV file.
Here's the model:
class BatchTask
def import(filename)
CSV.read(filename, :row_sep => "\r", :col_sep => ",")
end
end
Here's the spec code:
let(:data) { "title\tsurname\tfirstname\rtitle2\tsurname2\tfirstname2\r"}
let(:result) {[["title","surname","firstname"],["title2","surname2","firstname2"]] }
it "should parse file contents and return a result" do
File.stub(:open).with("file_name","rb") { StringIO.new(data) }
person.import("file_name").should == result
end
However, when I attempt to do this I get (stacktrace):
Errno::ENOENT in 'BatchTask should parse file contents and return a result'
No such file or directory - file_name
/Users/me/app/models/batch_task.rb:4:in `import'
./spec/models/batch_task_spec.rb:10:
Finished in 0.006032 seconds
I've been banging my head against this one and can't figure out what I'm doing wrong. Any help would be greatly appreciated!
回答1:
It would be helpful to provide a stacktrace, although I will make a guess why it happens. Also, I believe you're approach in here is not good and I will elaborate on how I believe you should test.
Simply, I think that CSV.read
does not use File.open
. It can use Kernel#open
or various other ways a file can be opened in Ruby. You should not stub File.open
in a test as this anyway.
There is a great book called Growing Object-Oriented Software Guided by Tests that has a need rule:
Stub only methods on classes/interfaces you control
There is a very simple reason for that. When you're doing test doubles (stub), the primary reason is interface discovery - you want to figure out how the interface of your class should look like and doubles provide you with neat feedback. There is also a secondary reason - stubbing external libraries tends to be quite tricky in some cases (when the library is not extremely stubbable). Thus, you can take a few different approaches here, that I will enumerate:
- You can test in integration. You can either create the file in each test and pass the pathname (which is fine).
- You can break down the way you're parsing. Instead of passing a filename to
CSV.read
, find a way when you pass an openFile
and then stub that in the test. i.e., have your code open the file instead ofCSV
. That way you can stub it easily - Stub
CSV.read
instead. That might be a bit dramatic, but in essence, you're not testing your code, you're testing theCSV
library. It should already have its own tests and you don't need to test it anyway. Instead, you can rely on the fact that it works and just stub the call to it.
Out of those, I would probably go with the third one. I don't like testing dependencies in my unit tests. But if you want your test to call that code, I suggest finding a way to do the second option (CSV.new(file)
should do the trick, but I don't have time to investigate) and finally fall back to #1 if nothing else works.
来源:https://stackoverflow.com/questions/11691722/stubbing-file-open-with-rspec