2

I'm using minitest to test a class with a method that raises an exception. I'm using a technique that is described in another question but I'm having some confusing results:

code:

class Formatter
  def output_report
    raise "Abstract method called: #{__method__}"
  end
end

test:

require 'minitest/autorun'
require_relative 'formatter'

class TestFormatter < Minitest::Test
  def setup
    @formatter = Formatter.new
  end

  def test_formatter_error_when_abstract_method_called
    err = assert_raises RuntimeError { @formatter.output_report }
    assert_equal "Abstract method called: output_report", err.message
  end
end

I was expected the test to pass, except instead I got the following error:

Run options: --seed 64742

# Running:

E

Finished in 0.001274s, 784.9294 runs/s, 0.0000 assertions/s.

  1) Error:
TestFormatter#test_formatter_error_when_abstract_method_called:
NoMethodError: undefined method `RuntimeError' for #<TestFormatter:0x007fd07a2b93d0>
    test_formatter.rb:10:in `test_formatter_error_when_abstract_method_called'

1 runs, 0 assertions, 0 failures, 1 errors, 0 skips

But then if I use the do...end block syntax instead of {...} then the test passes

do...end:

require 'minitest/autorun'
require_relative 'formatter'

class TestFormatter < Minitest::Test
  def setup
    @formatter = Formatter.new
  end

  def test_formatter_error_when_abstract_method_called
    # err = assert_raises RuntimeError { @formatter.output_report }
    err = assert_raises RuntimeError do
      @formatter.output_report
    end
    assert_equal "Abstract method called: output_report", err.message
  end
end

output:

Run options: --seed 37315

# Running:

.

Finished in 0.001298s, 770.4160 runs/s, 1540.8321 assertions/s.

1 runs, 2 assertions, 0 failures, 0 errors, 0 skips

Why is this happening? I thought do...end was equal to {...} except it's a convention to use the former when making a multi-line block?

Community
  • 1
  • 1
mbigras
  • 7,664
  • 11
  • 50
  • 111

1 Answers1

4

There is a significant difference between do..end and {...}: the precedence matters. Curlies are greedy, do...end are not.

Contrived example:

puts [1].each { |i| i }    #⇒ 1
puts [1].each do |i| i end #⇒ #<Enumerator:0x000000067dd678>

That said, to make curlies to work as expected, one should use parentheses:

assert_raises(RuntimeError) { @formatter.output_report }
Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
  • I think I get it, so in your first example the curlies are greedy, so the block gets run first. But in the second the `puts` prints the `[1].each` before it has a chance to run the block? And `[1].each` is an object that happens to be an enumerator, which is why we get some enumerator printed? `[1].each.class #=> Enumerator` – mbigras Nov 28 '16 at 17:42
  • 1
    @mbigras: to be more clear, the first is like this: `puts([1].each { |i| i } )`, wheras the second is like this: `puts([1].each) { |i| i }`. In the first case, the block is passed to `each` and executed, and then each returns `[1]` (because each just returns the receiver); in the second case, `[1].each` is evaluated without a block (and returns an Enumerator object), and then the block is passed to `puts` (which ignores it entirely). – philomory Nov 28 '16 at 20:46