Rails Test Prescriptions Blog

Keeping Your Application Healthy Since 2008

Creating, Sending, and Verifying CSV files using Comma

Here’s something I haven’t done in a while — a genuine code blog entry. I needed to add a simple CSV file output, here’s how I did it, tests and all.

I used two gems, FasterCSV, which I assume that most of you are familiar with, and Comma, which is a nice little DSL for specifying CSV formats. (Thanks again to Jason Pearl for reminding me what the gem was called…).

My Cucumber test for the feature looked like this (details have been changed to protect the innocent…)

  Background:
    Given I have valid adminstrative credentials
    And I have a user named "Charlie"
    And I have a user named "Bravo"
  Scenario: CSV Downloads
    When I go to the admin user page
    And I follow the link "CSV Report"
    Then I should see the table data:
        | Name       | Assigned  Number  | Email                |
        | Charlie    | 123435            | charlie@brandeis.edu |
        | Bravo      | 132134            | bravo@brandeis.edu   |

Just one note on this is that both the assigned number (which you can imagine as an id given to to the user by some other entity, such as a social security number) and the email, are deterministic and generated by the step definition called by the background task.

The downside of this Cuke test is that it’s a little more explicit than I normally like, since the numbers and the email are kind of magical here. That said, the ability to test the tabular CSV data from a Cuke table is powerful enough that this still seemed like the best way. Other opinions welcome.

The only step here that is undefined and interesting is the last one. And you might think it’d be complicated, but in fact it’s super easy:

Then /^I should see the table data:$/ do |table|
  actual_table = FasterCSV.parse(page.body)
  table.diff!(actual_table)
end

This does some complicated things compactly. The result of the eventual controller action is the text of the CSV file, and goes in page.body (since I’m using Capybara). FasterCSV parses that back into an array of arrays, and then I used Cucumber’s table compare method table#diff to compare the parsed CSV table to the table I passed in from the feature file. In theory, Cucumber provides a pretty output if the tables don’t match, but in practice I find I’m currently getting an error in the formatter.

This is nice, with the minor downside that the tables need to be an exact match. If that’s not feasible, then you can do more complex logic there to compare tables.

Next up was the model test, which I wrote more or less like this.

it "should produce expected csv" do
  @user_1 = Factory.build(:user, :name => "Fred", :assigned_number => 1, 
      :email => "fred@fred.com")
  @user_2 = Factory.build(:user, :name => "Barney", :assigned_number => 2, 
      :email => "b@b.com")
  @user_1.to_comma(:admin).should == ["Fred", "1", "fred@fred.com"]
  User.users_to_display.to_comma(:admin).should ==
      "Name,Assigned Number,Email\nBarney,2,b@b.com\,Fred,1,fred@fred.com\n"
  end

This test uses factory_girl to create two users, then uses to_comma to invoke the Comma gem, first on a single user, then on a list of users. As you can see, Comma is smart enough to return an array of data for a single object, but a CSV formatted string when given a list.

Technically, I think the last line is unnecessary, because assuming the first part of the test passes and I know that the list generating function works then I’m really just testing Comma. Of course, since I’m using Comma for the first time, testing it once seems appropriate.

It’s also worth noting that I’m showing the final form of these tests, I did a little test based exploration to see what the format was going to be. So I ran the test and just outputted the result of the to_comma, then wrote the test to match the format that I saw. Obviously, in that case you need to make sure that the format is what you want it to be, not just what the code outputs.

The Comma gem makes the model code super easy.

  comma(:admin) do
    name
    assigned_number
    email
  end

The comma method sets up a comma format, the optional symbol names the format relative to any other comma formats specified for the class. Within the block, the basic idea is that each expression specifies a column of the CSV file, header and all. In this case, I’m using the basic form, which uses attributes of the current instance. Although I’m not doing it here, Comma lets you specify a custom header name, and also allows you to call arbitrary methods of associated objects. I like it, it’s short, easy to read, and to the point.

Comma also lets you easily use the CSV format in a controller. Here’s what the controller method looks like:

  def index
    @users = User.users_to_display
    respond_to do |format|
      format.html #default
      format.csv do
        send_data(@users.to_comma(:admin),
            :filename => "users.csv", :type => "text/csv")
      end
    end
  end 

I didn’t add a controller test, on the grounds that there is no now controller logic and controller behavior is covered by the Cucumber test and the actual output is also covered by the model test (a previous controller test verifies that users_to_display is called correctly.

And that’s it. Turned out to be pretty easy.

Advertisements

One response to “Creating, Sending, and Verifying CSV files using Comma

  1. Pingback: Aug 27, 2010: Seek and You Shall Find « Rails Test Prescriptions Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: