Mock file input as file path on Rspec - ruby

I have a question on how to use rspec to mock a file input. I have a following code for the class, but not exactly know a why to mock a file input. filepath is /path/to/the/file
I did my search on Google and usually turns out to be loading the actual file instead of mocking, but I'm actually looking the opposite where only mock, but not using the actual file.
module Service
class Signing
def initialize(filepath)
#config = YAML.load_file(filepath)
raise "Missing config file." if #config.nil?
end
def sign() …
end
private
def which() …
end
end
end
Is it possible to use EOF delimiter for this file input mocking?
file = <<EOF
A_NAME: ABC
A_ALIAS: my_alias
EOF

You could stub out YAML.load_file and return parsed YAML from your text, like this:
yaml_text = <<-EOF
A_NAME: ABC
A_ALIAS: my_alias
EOF
yaml = YAML.load(yaml_text)
filepath = "bogus_filename.yml"
YAML.stub(:load_file).with(filepath).and_return(yaml)
This doesn't quite stub out the file load itself, but to do that you'd have to make assumptions about what YAML.load_file does under the covers, and that's not a good idea. Since it's safe to assume that the YAML implementation is already tested, you can use the code above to replace the entire call with your parsed-from-text fixture.
If you want to test that the correct filename is passed to load_file, replace the stub with an expectation:
YAML.should_receive(:load_file).with(filepath).and_return(yaml)

If the idea is to put an expectation on something, I don't see much benefit on this approach of calling YAML.load to fake the return. YAML.load_file actually returns a hash, so instead of doing all that my suggestion would be to simply return a hash:
parsed_yaml = {
"somekey" => {
"someotherkey" => "abc"
}
}
YAML.should_receive(:load_file).with(filepath).and_return(parsed_yaml)
As this is supposed to be a unit test and not an integration test, I think this would make more sense.

Related

Correct way to double a class instance with RSpec?

I'm trying to make a test double for a class instance with RSpec. Say I have a test that only accepts a File object as an argument.
Great, now how do I make a double so I don't have to pass in an actual file with all of my specs?
let(:file) { double(File) }
raise "NOT A FILE" unless file.is_a? File
# => RuntimeError: NOT A FILE
I've also tried this:
let(:file) { instance_double(File) }
raise "NOT A FILE" unless file.is_a? File
# => RuntimeError: NOT A FILE
And this (which is expecting an actual file):
let(:file) { object_double(File.new) }
# => ArgumentError: wrong number of arguments
What am I doing wrong?
You can just stub the is_a? call.
file = instance_double(File)
allow(file).to receive(:is_a?).with(File).and_return(true)
It is true you can stub #is_a?. But I would say, instead of trying to figure out what object it is, you could try to figure if it knows how to do the thing you need to do.
For instance, instead of received_file.is_a?(File), you could do received_file.respond_to?(:write).
This way, you can pass a Tempfile, or a File, or even an RSpec InstanceDouble(File). If the class knows how to #write then, we trust it.

Test nested File.open

I need to test a file open operation. I am able to test the first operation but not the second.
File.open("#{TemplateFile.fixture_path}/#{#template_file}") do |input_file|
template = ERB.new(input_file.read)
File.open("#{#project_name}/#{#destination_file}", 'w') do |output_file|
output_file.puts template.result binding
end
end
end
I am using this code:
module Pod
describe TemplateFile do
it "opens the template" do
dict = {"README.md.erb" => "README.md"}
File.expects(:open).with("#{TemplateFile.fixture_path}/README.md.erb")
File.expects(:open).with("Sample/README.md.erb", 'w')
TemplateFile.new(dict, "Sample")
end
end
end
But I am getting an error:
unsatisfied expectations:
- expected exactly once, not yet invoked: File.open('/README.md.erb', 'w')
satisfied expectations:
- expected exactly once, invoked once: File.open('/lib/pod/command/../../../fixtures/README.md.erb')
It seems that Mocha is not geeting the second File.open.
The reason is because expects verifies the call would happen but doesn't actually let it go through. So what's in the block doesn't get run.
However, beyond just telling you why it's not working, I also wanted to point out what you are doing is probably not what you want to do.
What you likely want to do do is:
template = ERB.new(File.read("#{TemplateFile.fixture_path}/#{#template_file}"))
File.open("#{#project_name}/#{#destination_file}", 'w') do |output_file|
output_file.puts template.result binding
end
You don't need that nesting.
Then when testing what you want to do to verify your the correct file is read is:
File.expects(:read).with("#{TemplateFile.fixture_path}/README.md.erb").returns(some_known_fixture)
The returns part says when it does get this read method with the specified argument I want you to return this known thing so that template will have a good value for the rest of the code.

Isolating specs to one specific call of a method

I am writing a spec for an object (Sample) that calls another object's method (IO#delete) a number of time.
I want to isolate the tests of this method, however when I do this:
class Sample
def delete_them
io.delete "file1"
io.delete "folder1"
end
end
describe Sample do
let(:io) { stub.as_null_object }
subject { Sample.new.tap { |s| s.stub(:io).and_return(io) }}
it "deletes file1" do
io.should_receive(:delete).with("file1")
subject.delete_them
end
it "deletes folder1" do
io.should_receive(:delete).with("folder1")
subject.delete_them
end
end
If I call multiple methods it's not a problem because I am using the null object pattern. However, in this case when I execute the second test, it complains:
1) Sample instance methods#delete_them deletes folder1
Failure/Error: io.should_receive(:delete).with("folder1")
Stub received :delete with unexpected arguments
expected: ("folder1")
got: ("file1")
Is there a way to indicate that all the calls must be ignored except the one I am trying to make sure is being done?
This code compiles fine. It was another issue that was causing my problem.

RSpec: how to test file operations and file content

In my app, I have the following code:
File.open "filename", "w" do |file|
file.write("text")
end
I want to test this code via RSpec. What are the best practices for doing this?
I would suggest using StringIO for this and making sure your SUT accepts a stream to write to instead of a filename. That way, different files or outputs can be used (more reusable), including the string IO (good for testing)
So in your test code (assuming your SUT instance is sutObject and the serializer is named writeStuffTo:
testIO = StringIO.new
sutObject.writeStuffTo testIO
testIO.string.should == "Hello, world!"
String IO behaves like an open file. So if the code already can work with a File object, it will work with StringIO.
For very simple i/o, you can just mock File. So, given:
def foo
File.open "filename", "w" do |file|
file.write("text")
end
end
then:
describe "foo" do
it "should create 'filename' and put 'text' in it" do
file = mock('file')
File.should_receive(:open).with("filename", "w").and_yield(file)
file.should_receive(:write).with("text")
foo
end
end
However, this approach falls flat in the presence of multiple reads/writes: simple refactorings which do not change the final state of the file can cause the test to break. In that case (and possibly in any case) you should prefer #Danny Staple's answer.
This is how to mock File (with rspec 3.4), so you could write to a buffer and check its content later:
it 'How to mock File.open for write with rspec 3.4' do
#buffer = StringIO.new()
#filename = "somefile.txt"
#content = "the content fo the file"
allow(File).to receive(:open).with(#filename,'w').and_yield( #buffer )
# call the function that writes to the file
File.open(#filename, 'w') {|f| f.write(#content)}
# reading the buffer and checking its content.
expect(#buffer.string).to eq(#content)
end
You can use fakefs.
It stubs filesystem and creates files in memory
You check with
File.exists? "filename"
if file was created.
You can also just read it with
File.open
and run expectation on its contents.
For someone like me who need to modify multiple files in multiple directories (e.g. generator for Rails), I use temp folder.
Dir.mktmpdir do |dir|
Dir.chdir(dir) do
# Generate a clean Rails folder
Rails::Generators::AppGenerator.start ['foo', '--skip-bundle']
File.open(File.join(dir, 'foo.txt'), 'w') {|f| f.write("write your stuff here") }
expect(File.exist?(File.join(dir, 'foo.txt'))).to eq(true)
end
end

RSpec mocking an :each block

I want to use RSpec mocks to provide canned input to a block.
Ruby:
class Parser
attr_accessor :extracted
def parse(fname)
File.open(fname).each do |line|
extracted = line if line =~ /^RCS file: (.*),v$/
end
end
end
RSpec:
describe Parser
before do
#parser = Parser.new
#lines = mock("lines")
#lines.stub!(:each)
File.stub!(:open).and_return(#lines)
end
it "should extract a filename into extracted" do
linetext = [ "RCS file: hello,v\n", "bla bla bla\n" ]
# HELP ME HERE ...
# the :each should be fed with 'linetext'
#lines.should_receive(:each)
#parser.should_receive('extracted=')
#parser.parse("somefile.txt")
end
end
It's a way to test that the internals of the block work correctly by passing fixtured data into it. But I can't figure out how to do the actual feeding with RSpec mocking mechanism.
update: looks like the problem was not with the linetext, but with the:
#parser.should_receive('extracted=')
it's not the way it's called, replacing it in the ruby code with self.extracted= helps a bit, but feels wrong somehow.
To flesh out the how 'and_yield' works: I don't think 'and_return' is really what you want here. That will set the return value of the File.open block, not the lines yielded to its block. To change the example slightly, say you have this:
Ruby
def parse(fname)
lines = []
File.open(fname){ |line| lines << line*2 }
end
Rspec
describe Parser do
it 'should yield each line' do
File.stub(:open).and_yield('first').and_yield('second')
parse('nofile.txt').should eq(['firstfirst','secondsecond'])
end
end
Will pass. If you replaced that line with an 'and_return' like
File.stub(:open).and_return(['first','second'])
It will fail because the block is being bypassed:
expected: ["firstfirst", "secondsecond"]
got: ["first", "second"]
So bottom line is use 'and_yield' to mock the input to 'each' type blocks. Use 'and_return' to mock the output of those blocks.
I don't have a computer with Ruby & RSpec available to check this, but I suspect you need to add a call to and_yields call [1] on the end of the should_receive(:each). However, you might find it simpler not to use mocks in this case e.g. you could return a StringIO instance containing linetext from the File.open stub.
[1] http://rspec.rubyforge.org/rspec/1.1.11/classes/Spec/Mocks/BaseExpectation.src/M000104.html
I would go with the idea of stubbing the File.open call
lines = "RCS file: hello,v\n", "bla bla bla\n"
File.stub!(:open).and_return(lines)
This should be good enough to test the code inside the loop.
This should do the trick:
describe Parser
before do
#parser = Parser.new
end
it "should extract a filename into extracted" do
linetext = [ "RCS file: hello,v\n", "bla bla bla\n" ]
File.should_receive(:open).with("somefile.txt").and_return(linetext)
#parser.parse("somefile.txt")
#parser.extracted.should == "hello"
end
end
There are some bugs in the Parser class (it won't pass the test), but that's how I'd write the test.

Resources