Michael Michael - 3 months ago 8
Ruby Question

How to test this CSV import rake task?

I have no idea where to start with testing this rake task. Do I need stubs? If so, how to use them? Any help would be appreciated. Thanks!

desc "Import CSV file"
task :import => [:environment] do
data = "db/data.csv"
headers = CSV.open(data, 'r') { |csv| csv.first }
cs = headers[2..-1].map { |c| Model1.where(name: c).first_or_create }
ls = Model2.find_ls

csv_contents = CSV.read(photos)
csv_contents.shift

csv_contents.each do |row|
p = Model2.where(id: row[0], f_name: row[1]).first_or_create
p_d = FastImage.size(p.file.url(:small))
p.update_attributes(dimensions: p_d)

row[2..-1].each_with_index do |ls, i|
unless ls.nil?
ls.split(',').each { |l|
cl = Model3.where(name: l.strip, model_1_id: cs[i].id).first_or_create
Model4.where(p_id: p.id, model_3_id: cl.id).first_or_create
}
end
end
end
end

Answer

Here's how I'd do it:

1) Happy-path test(s)

Rake tasks as such are a pain to test. Extract the body of the rake task into a class:

whatever.rake

desc "Import CSV file"
  task :import => [:environment] do
    CSVImporter.new.import "db/data.csv"
  end
end

lib/csv_importer.rb

class CsvImporter
  def import(data)
    headers = CSV.open(data, 'r') { |csv| csv.first }
    cs = headers[2..-1].map { |c| Model1.where(name: c).first_or_create }
    ls = Model2.find_ls

    csv_contents = CSV.read(photos)
    csv_contents.shift

    csv_contents.each do |row|
      p = Model2.where(id: row[0], f_name: row[1]).first_or_create
      p_d = FastImage.size(p.file.url(:small))
      p.update_attributes(dimensions: p_d)

      row[2..-1].each_with_index do |ls, i|
        unless ls.nil?
          ls.split(',').each { |l|
          cl = Model3.where(name: l.strip, model_1_id: cs[i].id).first_or_create
          Model4.where(p_id: p.id, model_3_id: cl.id).first_or_create
        }
      end
    end
  end
end

Now it's easy to write a test that calls CSVImporter.new.import on a test file (that's why import takes the file as a parameter instead of hardcoding it) and expects the results. If it's reasonable to import db/data.csv in the test environment, you can do that in a test if you want. You probably only need one test like this. No stubs are required.

2) Edge and error cases

There is a lot of logic here which, for simplicity and speed, you'll want to test without creating actual model objects. That is, yes, you'll want to stub. Model2.find_ls and FastImage.size are already easy to stub. Let's extract a method to make the other model calls easy to stub:

class CsvImporter
  def import(data)
    headers = CSV.open(data, 'r') { |csv| csv.first }
    cs = headers[2..-1].map { |c| Model1.first_or_create_with(name: c) }
    ls = Model2.find_ls

    csv_contents = CSV.read(photos)
    csv_contents.shift

    csv_contents.each do |row|
      p = Model2.first_or_create_with(id: row[0], f_name: row[1])
      p_d = FastImage.size(p.file.url(:small))
      p.update_attributes(dimensions: p_d)

      row[2..-1].each_with_index do |ls, i|
        unless ls.nil?
          ls.split(',').each { |l|
          cl = Model3.first_or_create_with(name: l.strip, model_1_id: cs[i].id)
          Model4.first_or_create_with(p_id: p.id, model_3_id: cl.id)
        }
      end
    end
  end
end

app/models/concerns/active_record_extensions.rb

module ActiveRecordExtensions
  def first_or_create_with(attributes)
    where(attributes).first_or_create
  end
end

and include the module in all of the models that need it.

Now it's easy to stub all of the model methods so you can write tests that simulate any database situation you like.

Comments