2

I'm trying to test a method that uses CSV.foreach to read in a csv file and does some processing on it. It looks something like this:

require 'csv'

class Csv_processor
  def self.process_csv(file)
    begin
      CSV.foreach(file) do { |row|
        # do some processing on the row
      end
    rescue CSV::MalformedCSVError
      return -1
    end
  end
end

CSV.foreach takes a file path as input, reads the file and parses the comma separated values, returning an array for each row in the file. Each array is passed in turn to the code block for processing.

I'd like to use Mocha to stub the foreach method so I can control the input to the process_csv method from my test without any file I/O mumbo-jumbo.

So the test would be something like this

test "rejects input with syntax errors" do
  test_input = ['"sytax error" 1, 2, 3', '4, 5, 6', '7, 8, 9']
  CSV.expects(:foreach).returns( ??? what goes here ??? )
  status = Csv_processor.process_csv("dummy")
  assert_equal -1, status, "Status does not indicate error: #{status}"
end

I need a way to turn my test_input array into something Mocha can work with. I think I have to use some sort of proc or lambda, but I find the world of procs, blocks and lambdas a bit of a mind-bender.

Vega
  • 27,856
  • 27
  • 95
  • 103
Mike E
  • 5,493
  • 1
  • 14
  • 15
  • 1
    Note that the more mocking you use the more the test is coupled with the implementation. You change the csv parser and suddenly your tests stops working. Note also that: 1) `return -1` is C-style, not idiomatic Ruby at all (return `nil` instead). 2) You can `rescue` at the `def` level. – tokland Dec 07 '11 at 20:52
  • Thanks for your comments tokland. And you're right about mocking, I know. Maybe I should be looking harder for an alternative. I appreciate the other two notes too. I'm pretty new to Ruby as you can probably tell. – Mike E Dec 07 '11 at 21:00
  • Can CSV read in a string representing the contents of a CSV file? That way you'd avoid IO. – Andrew Grimm Dec 07 '11 at 21:51
  • CSV has methods for handling string input, but the test I want to write has to verify things like whether I/O errors are handled correctly. Using the string methods misses the point. – Mike E Dec 07 '11 at 22:14

2 Answers2

4

Use Mocha::Expectations#multiple_yields:

CSV.expects(:foreach).multiple_yields([row_array1], [row_array2], ...)

Check this thread to see why you have to pass the rows inside another array.

tokland
  • 66,169
  • 13
  • 144
  • 170
  • I jumped the gun in accepting this solution. There's a hic-up when I try this. Only the first array elements gets assigned to row. Using a splat (i.e. |*row|) allows me to get the whole array, but a splat doesn't work with the real foreach method. – Mike E Dec 07 '11 at 22:09
  • 2
    @Mike E. Given that the rows are arrays, it's a special case. Try: multiple_yield([row1], [row2]) – tokland Dec 07 '11 at 22:14
  • That's the ticket! The link you included in your answer was very helpful in explaining the need for the additional nesting also. – Mike E Dec 07 '11 at 22:48
0

use a Proc object as return value:

Proc.new{ rand(100) }
zed_0xff
  • 32,417
  • 7
  • 53
  • 72