Reputation: 1666
It's fairly common in Ruby for methods that take blocks to look like this:
class File
def open(path, mode)
perform_some_setup
yield
ensure
do_some_teardown
end
end
It's also fairly idiomatic for a method to look like this:
def frobnicate
File.open('/path/to/something', 'r') do |f|
f.grep(/foo/).first
end
end
I want to write a spec for this that doesn't hit the filesystem, which ensures it pulls the right word out of the file, something like:
describe 'frobnicate' do
it 'returns the first line containing the substring foo' do
File.expects(:open).yields(StringIO.new(<<EOF))
not this line
foo bar baz
not this line either
EOF
expect(frobnicate).to match(/foo bar baz/)
end
end
The problem here is that, by mocking out the call to File.open
, I've also removed its return value, which means that frobnicate
will return nil
. If I were to add something like File.returns('foo bar baz')
to the chain, however, I'd end up with a test that doesn't actually hit any of the code I'm interested in; the contents of the block in frobnicate
could do anything and the test would still pass.
How might I appropriately test my frobnicate
method without hitting the filesystem? I'm not particularly attached to any particular testing framework, so if your answer is "use this awesome gem that'll do it for you" then I'm OK with that.
Upvotes: 3
Views: 1696
Reputation: 2061
Using minitest it could be done like I post below. I have added the whole runnable file, so you can test it from the command line with ruby -Ilib:test test_file.rb
:
def frobnicate
found_string = nil
File.open('/path/to/something', 'r') do |f|
found_string = f.grep(/foo/).first
end
found_string
end
class FrabnicateTest < Minitest::Test
def test_it_works
mock_file = StringIO.new(%(
not this line
foo bar baz
not hthis line either
))
search_result = nil
File.stub(:open, nil, mock_file) do
search_result = frobnicate
end
assert_match(/foo bar baz/, search_result)
end
end
Upvotes: 1
Reputation: 777
It seems like you just need to mock the call to File
a little differently. I was getting syntax errors running your code as-is, so I'm not sure what version of RSpec you're on, but if you're on 3.x this will do the job:
gem 'rspec', '~> 3.4.0'
require 'rspec/autorun'
RSpec.configure do |config|
config.mock_with :rspec
end
def frobnicate
File.open('/path/to/something', 'r') do |f|
f.grep(/foo/).first
end
end
RSpec.describe 'frobnicate' do
it 'returns the first line containing the substring foo' do
allow(File).to receive(:open).and_call_original
allow(File).to receive(:open).and_yield StringIO.new <<-EOF
not this line
foo bar baz
not this line either
EOF
expect(frobnicate).to match(/foo bar baz/)
end
end
Invoke with ruby frobnicate_spec.rb
so we can use a specified RSpec version.
Source: RSpec Mocks expecting messages and yielding responses
Upvotes: 2