Charles
Charles

Reputation: 379

Testing input/output with rspec and plain ruby

I am trying to create a test for a FileProcessor that reads from a text file, passes it to another class and then writes output. I made a test file and am able to access but it feels bulky. I'm also going to need to test that it writes the output in a new file and I am not sure how to set this up. I've seen a lot of tutorials but they are be rails centric. My goal is to get rid of writing the path in the test and to clean up the generated output files after each test.

describe FileProcessor do 

  test_file = File.dirname(__FILE__) + '/fixtures/test_input.txt'
  output_file = File.dirname(__FILE__) + '/fixtures/test_output.txt'

  subject {FileProcessor.new(test_file, output_file)}

  describe '#read_file' do
    it 'reads a file' do
      expect(subject.read_file).to eq('This is a test.')
    end
  end

  def write_file(str)
   File.open("#{output_file}", "w+") { |file| file.write(str) }
  end


end

Upvotes: 0

Views: 1376

Answers (2)

7stud
7stud

Reputation: 48589

How about using StringIO:

require 'stringio'

class FileProcessor

  def initialize(infile, outfile)
    @infile = infile
    @outfile = outfile
    @content = nil
  end

  def read_file
    @content ||= @infile.read
  end

  def write_file(text)
    @outfile.write(text)
  end
end

describe FileProcessor do 
    let(:outfile) { StringIO.new }

    subject(:file_processor) do
      infile = StringIO.new('This is a test')
      FileProcessor.new(infile, outfile)
    end

    describe '#read_file' do
      it "returns correct text" do
        expect(file_processor.read_file).to eq("This is a test")
      end
    end

    describe '#write_file' do
      it "writes correct text" do
        file_processor.write_file("Hello world")

        outfile.rewind
        expect(outfile.read).to eq("Hello world")
      end
    end
end

Upvotes: 3

Jordan Running
Jordan Running

Reputation: 106027

There's not a great way to avoid writing the path of your input file. You could move that into a helper method, but on the other hand having the path in the test has the benefit that someone else (or you six months from now) looking at the code will know immediately where the test data comes from.

As for the output file, the simplest solution is to use Ruby's built-in Tempfile class. Tempfile.new is like File.new, except that it automatically puts the file in /tmp (or wherever your OS's temporary file directory is) and gives it a unique name. This way you don't have to worry about cleaning it up, because the next time you run the test it'll use a file with a different name (and your OS will automatically delete the file). For example:

require 'tempfile'

describe FileProcessor do
  let(:test_file_path) { File.dirname(__FILE__) + '/fixtures/test_input.txt' }
  let(:output_file) { Tempfile.new('test_output.txt').path }

  subject { FileProcessor.new(test_file_path, output_file.path) }

  describe '#read_file' do
    it 'reads a file' do
      expect(subject.read_file).to eq('This is a test.')
    end
  end
end

Using let (instead of just assigning a local variable) ensures that each example will use its own unique output file. In RSpec you should almost always prefer let.

If you want to get really serious, you could instead use the FakeFS gem, which mocks all of Ruby's built-in file-related classes (File, Pathname, etc.) so you're never writing to your actual filesystem. Here's a quick tutorial on using FakeFS: http://www.bignerdranch.com/blog/fake-it/

Upvotes: 2

Related Questions