tags:

views:

237

answers:

3

I'm writing a RubyGem that can raise an ArgumentError if the arguments supplied to its single method are invalid. How can I write a test for this using RSpec?

The example below shows the sort of implementation I have in mind. The bar method expects a single boolean argument (:baz), the type of which is checked to make sure that it actually is a boolean:

module Foo
  def self.bar(options = {})
    baz = options.fetch(:baz, true)
    validate_arguments(baz)
  end

  private
  def self.validate_arguments(baz)
    raise(ArgumentError, ":baz must be a boolean") unless valid_baz?(baz)
  end

  def self.valid_baz?(baz)
    baz.is_a?(TrueClass) || baz.is_a?(FalseClass)
  end
end
+2  A: 
it "should raise ArgumentError for arguments that are not boolean" do
  lambda{ Foo.validate_arguments(something_non_boolean) }.should raise_error(ArgumentError)
end
JHurrah
Thank you. I've edited the original question because I forgot to indicate that the `validate_arguments` and `valid_baz?` methods are private. So I'm doing `lambda{ Foo.bar(:baz => 'a') }.should raise_error(ArgumentError)` which passes. However, the test still passes if I set `:baz` to a boolean in the test - any idea why?
John Topley
I like to `alias :running :lambda` in my `spec_helper.rb` so that I can write `running { ... }.should raise_error`.
Theo
@Theo That's a nice idea.
John Topley
@Theo that's some nice sugar, thanks.@John Topley, Im not sure whats going on. Your code works as expected on my machine.
JHurrah
+1  A: 

Unless it's very important that you throw an exception for non-boolean values, I think it would be more elegant to coerce the value to a boolean, for example with !!:

baz = !! options.fetch(:baz, true)

That way the client code can pass any truthy or falsy value, but you can still be sure that the value you work with is a proper boolean. You could also use the ternary operator (e.g. baz = options.fetch(:baz, true) ? true : false if you feel that !! is unclear.

Theo
I take your point, but my `validate_arguments` method actually also validates other arguments passed to the method, using more complex validation logic.
John Topley
fair enough... but use this for the booleans, it's annoying to use code that checks types in a dynamic language.
Theo
@Theo OK, will do!
John Topley
+2  A: 

I use something similar to what JHurra posted:

it "should raise ArgumentError for arguments that are not boolean" do
  expect{ Foo.validate_arguments(nil) }.to raise_error(ArgumentError)
end

No need to alias (rspec 1.3).

Ragmaanir
Thanks. Does RSpec automatically have access to private methods? My `validate_arguments` method is private.
John Topley
The method is not private because it is a class method and the private keyword does only apply to instance methods. Make the methods instance methods or use `private_class_method :mymethodname` to make the class methods private.
Ragmaanir
I didn't know that - thanks.
John Topley