Better CSV Specs

I’ve always found testing CSVs with RSpec to be pretty painful. Generally, CSVs are tested like this:

expect(my_row).to eq(['First Name', 'Last Name'])

This has two major downsides:

  • The spec becomes harder to read and maintain as the number of rows and columns you are interested in testing grows.
  • When the test fails, the output RSpec gives you can be hard to read or even useless. Often it can look like the following:
expected: ["elements", "at", "the", "start" "..., "elements", "at", "the", "end"]
     got: ["elements", "at", "the", "start" "..., "elements", "at", "the", "end"]

(compared using ==)

This isn’t helpful when the difference is somewhere in the middle of the row!

Recently, I’ve created a helper which looks like this:

module ReportsSpecHelper
  def expect_csv_to_equal(expected, actual)
    expected_titles = expected.first

    expected.each_with_index do |row, row_index|
      row.each_with_index do |cell, column_index|
        actual_value = actual[row_index][column_index]
        expect(actual_value).to eq(cell), lambda {
          value_for_string = if row_index > 0
                               "value for #{expected_titles[column_index]}"
                             else
                               "value"
                             end
          "Cell #{row_index}:#{column_index} (#{actual_value}) did not match expected #{value_for_string} (#{cell})."
        }
      end
    end
  end
end

I use this to compare the CSV under test with a CSV I have stored in the same directory as the spec. It iterates every row and column in the CSV and performs a comparison.

This has two advantages:

  • I can read and edit the expected CSV in a CSV editor (Rubymine has one built in).
  • If it fails I get a nice error message:
"Cell 2:1 (Peter) did not match expect value for First Name (Pete)"
Written on May 22, 2018