I am trying to use RSpec to test a class that writes to a file. But I want the tests to be fast, so instead of using a real file and write to disk I want to use StringIO in tests and write to memory.
In a very simplified way, let's say I have this test:
RSpec.describe Writer do
it 'replaces the contents of the file' do
file = StringIO.new('foo')
writer = described_class.new(file)
one_contact = [{ 'name' => 'name', 'address' => 'address' }]
writer.write(one_contact)
expect(file.string).to eq('[{"name":"name1","address":"address1"}]')
end
end
and let's say the writer class is like this:
require 'json'
class Writer
def initialize(file)
@file = file
end
def write(contacts)
file.truncate(0)
file.write(contacts.to_json)
file.flush
end
private
attr_reader :file
end
when I run the tests through Rspec I get the error:
Failure/Error: file.truncate(0)
IOError:
not opened for writing
If I change truncate
for something else then I get this error in the line that calls write
on file
.
However, if I do it in pry, it works:
$ pry
[1] pry(main)> require_relative 'lib/db/writer'
=> true
[2] pry(main)> file = StringIO.new('foo')
=> #<StringIO:0x0000563c99220f70>
[3] pry(main)> writer = Writer.new(file)
=> #<Writer:0x0000563c98ff8950 @file=#<StringIO:0x0000563c99220f70>>
[4] pry(main)> one_contact = [{ 'name' => 'name', 'address' => 'address' }]
=> [{"name"=>"name", "address"=>"address"}]
[5] pry(main)> writer.write(one_contact)
=> #<StringIO:0x0000563c99220f70>
[6] pry(main)> file.string
=> "[{\"name\":\"name\",\"address\":\"address\"}]"
If I do it in a normal ruby file and run it with ruby, for example:
require_relative 'lib/db/writer'
file = StringIO.new('foo')
writer = Writer.new(file)
one_contact = [{ 'name' => 'name1', 'address' => 'address1' }]
writer.write(one_contact)
puts file.string
It works as well.
This is a super simple Ruby app, no dependencies or frameworks, this is what the Gemfile looks like:
source 'https://rubygems.org'
ruby '2.5.1'
gem 'sqlite3'
group :test do
gem 'coveralls', require: false
gem 'pry'
gem 'rake'
gem 'rspec'
gem 'rubocop', require: false
end
Actual gem versions:
How can I open a StringIO for writing? I want to keep writing to memory rather than disk on tests.
Here's your problem:
# frozen_string_literal: true
Your string contents
is frozen and can't be modified. StringIO expresses it with the abovementioned IOError
.