Returning a StringIO object from a stub

I’m trying to stub File.new so I can return a StringIO object from it
to set some expectation and make sure the subject under test is
behaving correctly, but calling readline() on the StringIO object in
the subject always returns nil. What’s strange is that calling read()
returns the entire set of data in the StringIO object. Additionally,
calling readline() on the StringIO object from inside the test works
fine as well.

data = <<-DATA

DATA

faux_file = StringIO.new(data)
File.stub!(:new).and_return(faux_file)
faux_file.should_receive(:readline).exactly(x).times
Subject.new("").parse

I’m using rspec 1.2.9 (this is not testing a rails app). Can anyone
give me any advice on why this might be?

Thanks,

Jeremy

JGailor [email protected] on 2009-11-11 at 16:11:

File.stub!(:new).and_return(faux_file)
faux_file.should_receive(:readline).exactly(x).times
Subject.new(“”).parse

As soon as you call should_receive(:readline), you are not only
setting an
expectation for that method to be received but also stubbing out the
method.

Here’s a bit more in-depth discussion of that:

That link also discusses why that might be a good idea – it keeps specs
simple.

So you’ve got two options,

(1) If you’ve got your heart set on counting the number of calls to
readline
you can do something like the following:

faux_file.should_receive(:readline).exactly(x).times.and_return(*data.readlines)

Which, as you can see, is basically a funky re-implementation of
StringIO#readline, but it should do the trick.

(2) I would encourage you to consider this: rather than asserting that
every
line is read, simply verify that the result of your Subject#parse
method
includes data that would prove that each line has been read. That
way
you’re testing behavior rather than implementation.

Cheers,

Paul

Thanks Paul,

As soon as I read the first part of your message I had one of those
face-palm moments. Thanks for your help. In the case of this
expectation, I’m not sure which path I’ll take as this particular test
is looking for it to read a certain number of lines of input before
finding the target line.