Forum: Ruby assert_raise question

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
C2479d02d5795b2186ed86547dcba098?d=identicon&s=25 Sean Carley (Guest)
on 2006-03-08 22:43
(Received via mailing list)
Currently, assert_raise expects you to know exactly what kind of
exception
is going
to be thrown by raise.  Basically it tests to see if exception.class ==
expected.class.
I think a more appropriate behavior would be to test
exception.kind_of?expected.  I
would also be happy if there were one method that performed the way it
does
currently
and another that checks exception parentage.

Please see the code below for an example of two tests.  I think both
should
pass but
only the first test does because ArgumentError != StandardError.

Sean Carley

### Begin test.rb ###
require 'test/unit'
class TestRaise < Test::Unit::TestCase
  def test_raise
    assert_raise(ArgumentError){raise ArgumentError.new("basic argument
issue")}
  end
  def test_raise_parent
    assert_raise(StandardError){raise ArgumentError.new("basic argument
issue")}
  end
end
###  End test.rb  ###

> ruby test.rb
Loaded suite test
Started
.F
Finished in 0.018356 seconds.

  1) Failure:
test_raise_parent(TestRaise) [test.rb:7]:
<StandardError> exception expected but was
Class: <ArgumentError>
Message: <"basic argument issue">
---Backtrace---
test.rb:7:in `test_raise_parent'
test.rb:7:in `test_raise_parent'
---------------

2 tests, 2 assertions, 1 failures, 0 errors
10d4acbfdaccb4eee687a428ca00a5d8?d=identicon&s=25 Jim Weirich (weirich)
on 2006-03-08 22:45
Sean Carley wrote:
[...]
> I think a more appropriate behavior would be to test
> exception.kind_of?expected.  I would also be happy if
> there were one method that performed the way it
> does currently and another that checks exception parentage.

+1

--
-- Jim Weirich
58479f76374a3ba3c69b9804163f39f4?d=identicon&s=25 Eric Hodel (Guest)
on 2006-03-08 23:59
(Received via mailing list)
On Mar 8, 2006, at 1:42 PM, Sean Carley wrote:

> Currently, assert_raise expects you to know exactly what kind of
> exception
> is going to be thrown by raise.  Basically it tests to see if
> exception.class ==
> expected.class.
>
> I think a more appropriate behavior would be to test
> exception.kind_of?expected.  I would also be happy if there were
> one method that performed the way it does currently and another
> that checks exception parentage.

For a given input a method should only raise one class of exception.
The restriction forces you to write better tests.

> Please see the code below for an example of two tests.  I think
> both should
> pass but only the first test does because ArgumentError !=
> StandardError.

require 'socket'
require 'test/unit'

class MyClass
   def connect(overthere)
     unless overthere =~ /:\d+\Z/ then
       raise ArgumentError, "Use host:port, got #{overthere}"
     end
     host, port = overthere.split ':'
     @socket = TCPSocket.new host, port.to_i
   end
end

This method can raise at least two different exceptions on bad input
so you should test for them separately.  This allows consumers of
your code to behave properly depending upon what arguments they feed
your code.

class TestMyClass < Test::Unit::TestCase

   def setup
     @obj = MyClass.new
   end

   def test_connect_bad_args_no_port
     assert_raises ArgumentError do
       @obj.connect 'host'
     end
   end

   def test_connect_bad_args_bad_port
     assert_raises ArgumentError do
       @obj.connect 'host:port'
     end
   end

   def test_connect_bad_host
     assert_raises SocketError do
       @obj.connect 'no-such-host.example.com:80'
     end
   end

end

--
Eric Hodel - drbrain@segment7.net - http://blog.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com
10d4acbfdaccb4eee687a428ca00a5d8?d=identicon&s=25 Jim Weirich (weirich)
on 2006-03-09 02:29
Eric Hodel wrote:
> For a given input a method should only raise one class of exception.
> The restriction forces you to write better tests.

The reason I would like it is that the test is the specification of the
behavior.  I often wish to specify that a method will raise a more
general exception than the one it actually raises.  The actual exception
raised may be an implementation detail that I don't wish to have
expressed in the spec.

--
-- Jim Weirich
F3b786a23390451fa67108781b0c8fed?d=identicon&s=25 unknown (Guest)
on 2006-03-09 06:06
(Received via mailing list)
+1 again.

A big part of UT is to allow ease of refactoring.  Part of refactoring
is, IMO, making exceptions more specific (eg from raise "No record
found" to raise DBException, "No record found") and the like.

Having to change your UT's when you want to refactor is a bit self
defeating.
1cc072ab8daecee4dc8bca69fc5d574c?d=identicon&s=25 Edwin Fine (efine)
on 2006-11-20 23:58
unknown wrote:
> +1 again.
>
> A big part of UT is to allow ease of refactoring.  Part of refactoring
> is, IMO, making exceptions more specific (eg from raise "No record
> found" to raise DBException, "No record found") and the like.
>
> Having to change your UT's when you want to refactor is a bit self
> defeating.

Agreed.

Another +1. The whole point of having an exception hierarchy is so that
you can specify a parent exception class at the interface level. You do
this so that as your software grows, you can add new sub-exceptions
without breaking your unit tests or your client's code. Sometimes you
are interested in the actual exception, but sometimes you just want to
catch the more general exception and don't care what the specific class
is.

Here's an example:

begin
   eval(some text)
rescue ScriptError => e
   # Handle the script error
end

Do we always care if it was a ScriptError descendant (shown below)?

   LoadError
   NotImplementedError
   SyntaxError

No. Not always. Maybe not even in a unit test. Maybe the unit test
starts off more general, and then as time goes by gets refined to test
the exact exceptions.

I believe it's better to allow the users of general library code to use
it the way that suits them best. So, I suggest adding another assertion
method, assert_raise_s, which will succeed if the argument class of the
raised exception is derived from (<=) the expected class. That way, you
can have both a strict assertion (assert_raise) and the more general
case (assert_raise_s).

In the meantime, I will just add a local assert_raise_s because, thanks
to Matz, Ruby classes are open to extension.
9de990c098cf1277d9ffe48f42011ef8?d=identicon&s=25 Doug Glidden (carluva)
on 2008-08-27 16:28
[snip]
> ### Begin test.rb ###
> require 'test/unit'
> class TestRaise < Test::Unit::TestCase
>   def test_raise
>     assert_raise(ArgumentError){raise ArgumentError.new("basic argument
> issue")}
>   end
>   def test_raise_parent
>     assert_raise(StandardError){raise ArgumentError.new("basic argument
> issue")}
>   end
> end
> ###  End test.rb  ###
[snip]

This is a somewhat unrelated question, but in playing around with this,
I discovered something odd.  The test_raise method above works, but the
following modification of it does not:

def test_raise
  assert_raise ArgumentError {raise ArgumentError.new("basic argument
issue")}
end

Now there's suddenly an Exception: undefined method 'ArgumentError'...
I was more than a little surprised to discover that the parens make a
difference here?  Can anyone explain to me _why_ there's a difference?

Doug
40a3939023ef722e64e4c255d6d5bcd9?d=identicon&s=25 Fearless Fool (fearless_fool)
on 2010-06-02 22:49
+1 also.

It might make sense to define:
  assert_something_raised
to parallel the existing
  assert_nothing_raised

- ff
703fbc991fd63e0e1db54dca9ea31b53?d=identicon&s=25 Robert Dober (Guest)
on 2010-06-03 17:01
(Received via mailing list)
On Thu, Mar 9, 2006 at 3:29 AM, Jim Weirich <jim@weirichhouse.org>
wrote:
> --
> -- Jim Weirich

There is no way you are wrong, thus you are both right :P.
Personally I prefer rspec's behavior, also because it matches the
semantics of "rescue".
But I would like to raise a question myself - I just broke the record
for the cheapest pun - why not have two different assertions for being
able to test/specify two different things.

Cheers
R.
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (candlerb)
on 2010-06-04 15:54
Doug Glidden wrote:
> def test_raise
>   assert_raise ArgumentError {raise ArgumentError.new("basic argument
> issue")}
> end
>
> Now there's suddenly an Exception: undefined method 'ArgumentError'...
> I was more than a little surprised to discover that the parens make a
> difference here?  Can anyone explain to me _why_ there's a difference?

foo { ... }  is parsed as a method call with a block.

So your code is parsed as:

  assert_raise(ArgumentError() { mode code })

Method names which start with a capital letter are fairly infrequent,
but are allowed - see for example Integer(x) or Array(x).

Using do...end instead of {...} tips the parsing the right way:

  assert_raise ArgumentError do
    raise ArgumentError.new "basic argument issue"
  end
This topic is locked and can not be replied to.