0

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

2 Answers 2

3

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
Sign up to request clarification or add additional context in comments.

Comments

2

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/

3 Comments

Not sure if this is the best way to ask a follow up question. My write method takes in a string, I've add it above. Should I use fakefs to mock the file system in that case?
Not necessarily. I've changed my answer to show a slightly different approach, which is having output_file return the Tempfile object (and in the subject block use output_file.path to get its path). With that change, you can just call output_file.write to write to the file. I hope that's helpful!
You've been more than helpful Jordan!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.