Ruby: rspec not catching exceptions? - ruby

I'm having trouble grasping how rspec handles exceptions. In this case, I'd like to ensure the exception is properly caught and returned in a formatted message.
Consider the following:
class Sample
def calculate(x, y)
begin
add(x,y)
rescue Exception => e
return "Exception occured: #{e}"
end
end
def add(x, y)
x + y
end
end
With a spec like so:
describe "sample" do
it "adds numbers" do
test = Sample.new
allow(test)
.to receive(:calculate)
.with(1, 2) { Exception.new('foo') }
expect(test.calculate(1,2)). to eq 'Exception occurred: foo'
end
end
I would expect the exception to be properly caught by rspec as it is by the method calculate itself, however, I receive the following when executing the spec:
Failures:
1) sample adds numbers
Failure/Error: expect(test.calculate(1,2)). to eq 'Exception occurred: foo'
expected: "Exception occurred: foo"
got: #<Exception: foo>
(compared using ==)
Diff:
## -1,2 +1,2 ##
-"Exception occurred: foo"
+#<Exception: foo>
But when testing the rescue via irb, it's properly handled:
irb(main):001:0> test = Sample.new()
=> #<Sample:0x000055d0f864db50>
irb(main):002:0> test.calculate("i", 2)
=> "Exception occured: no implicit conversion of Integer into String"
irb(main):003:0>
I'm very new to rspec so any feedback is welcome :D

Uou need to stub add method instead of calculate and instead of returning the Exception you need to raise the Exception.
describe "sample" do
it "adds numbers" do
test = Sample.new
allow(test)
.to receive(:add)
.with(1, 2).and_raise(Exception.new('foo'))
expect(test.calculate(1,2)). to eq 'Exception occurred: foo'
end
end

Related

(RSpec) How do I check if an object is created?

I want to test if expected exception handling is taking place in the following Ruby code through RSpec. Through testing I realized that I cannot use the raise_error matcher to test if the exception was raised, after rescuing it.
So, now I want to test whether objects of CustomError and StandardError are created to see if the error was raised as expected.
test.rb
module TestModule
class Test
class CustomError < StandardError
end
def self.func(arg1, arg2)
raise CustomError, 'Inside CustomError' if arg1 >= 10 && arg2 <= -10
raise StandardError, 'Inside StandardError' if arg1.zero? && arg2.zero?
rescue CustomError => e
puts 'Rescuing CustomError'
puts e.exception
rescue StandardError => e
puts 'Rescuing StandardError'
puts e.exception
ensure
puts "arg1: #{arg1}, arg2: #{arg2}\n"
end
end
end
test_spec.rb
require './test'
module TestModule
describe Test do
describe '#func' do
it 'raises CustomError when arg1 >= 10 and arg2 <= -10' do
described_class.func(11, -11)
expect(described_class::CustomError).to receive(:new)
end
end
end
end
When I run the above code I get the following error
Failures:
1) TestModule::Test#func raises CustomError when arg1 >= 10 and arg2 <= -10
Failure/Error: expect(described_class::CustomError).to receive(:new)
(TestModule::Test::CustomError (class)).new(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
# ./test_spec.rb:8:in `block (3 levels) in <module:TestModule>'
The idea was that if CustomError is being raised, it's obejct must be created using new and I can test that using RSpec. However, as you can see, this isn't working.
What am I missing?
Is there a better approach?
What you are trying to do is to test implementation details (what happens inside the method), and usually it's a bad idea. If a particular path leads to an exception but you want this exception to be swallowed - test that.
consider
def weird_division(x, y)
x/y
rescue ZeroDivisionError => e
return "Boom!"
end
No need to test that ZeroDivisionError has been created, that's an implementation detail (akin to testing private methods). Test behavior that is "visible" from the outside.
expect(weird_division(1/0)).to return "Boom!"
Because you might change the implementation:
def weird_division(x, y)
return "Boom!" if y == 0
x/y
end
And your tests would start failing, even though the method behaves the same.
I see two possibilities.
If you want to continue with your assertion you can use have_received instead of receive. This will allow you to keep you assertion after you act.
expect(described_class::CustomError).to have_received(:new)
As mentioned in comments test your puts. Which is ultimately the way that you check if your method does what you want.
it 'puts the output' do
expect do
described_class.func(11, -11)
end.to output('Some string you want to print').to_stdout
end

RSpec: TypeError When Stubbing Custom Exception

I'd like to test that Custom::Runner.run rescues all StandardErrors and fires an alert with the exception.
I'm having some trouble figuring out how to stub the call to my custom error class, Custom::Error, and expecting that Custom::Alert.error was received with the double as an argument.
Here's a complete test case to demonstrate the issue:
module Custom
class Error < StandardError
end
end
module Custom
class Alert
def self.error(exception, context = {})
end
end
end
module Custom
class Runner
def self.run
request
rescue => e
Custom::Alert.error(e, { foo: :bar })
end
class << self
def request
raise Custom::Error.new('test')
end
end
end
end
Here's the test:
RSpec.describe Custom::Runner do
describe '.run' do
let(:custom_error_double) { instance_double(Custom::Error) }
before do
# could this be the culprit?
allow(Custom::Error).to receive(:new)
.with('test')
.and_return(custom_error_double, 'test')
end
it 'fires a custom alert' do
expect(Custom::Alert).to receive(:error)
.with(custom_error_double, foo: :bar)
described_class.run
end
end
end
The test fails:
Failures:
1) Custom::Runner.run fires a custom alert
Failure/Error: Custom::Alert.error(e, { foo: :bar })
#<Custom::Alert (class)> received :error with unexpected arguments
expected: (#<InstanceDouble(Custom::Error) (anonymous)>, {:foo=>:bar})
got: (#<TypeError: exception class/object expected>, {:foo=>:bar})
Diff:
## -1,2 +1,2 ##
-[#<InstanceDouble(Custom::Error) (anonymous)>, {:foo=>:bar}]
+[#<TypeError: exception class/object expected>, {:foo=>:bar}]
I believe this is because rescue requires an exception and I'm returning a double. I've tried raising .and_raise(custom_error_double), but I continue to get the same TypeError: exception class/object expected.
There must be something I'm missing here. Any advice would be appreciated.
I guess the instance double of Custom::Error is an InstanceDouble object and not an Exception object so when you raise the double it causes the TypeError.
You could replace the double
let(:custom_error_double) { instance_double(Custom::Error) }
with a real Custom::Error object
let(:custom_error) { Custom::Error.new }
to avoid that.
I believe that you have it exactly right that the exception vs. the double is the issue. The error specifically is received :error with unexpected arguments, and the comparison is that the double does not match the TypeError. In this case, the blind rescue => e then calls Custom::Alert.error(e, {foo: :bar}) (which has the TypeError as the argument e), but in your test the .with() is expecting the double.
This will work:
RSpec.describe Custom::Runner do
describe '.run' do
let(:custom_error) { Custom::Error.new }
before do
allow(Custom::Error).to receive(:new).with('test').and_return(custom_error, 'test')
end
it 'fires a custom alert' do
expect(Custom::Alert).to receive(:error).with(custom_error, foo: :bar)
described_class.run
end
end
end

Can you rescue from specific errors with messages in ruby?

I'm trying to understand how errors propogate between classes in Ruby. I have this so far:
class User
def charge
puts "charging order soon"
raise RuntimeError.new("This is a runtime error")
rescue ArgumentError
puts "should never gets here"
end
end
class Runner
def run
begin
User.new.charge
rescue RuntimeError => e
puts e.message
end
end
end
Runner.new.run
When I run this, I get this which seems right:
$ ruby errors.rb
charging order soon
This is a runtime error
Inside runner, can I rescue from the RuntimeError with a specific message? If I have multiple RuntimeErrors being raised around my application, is there any way for the Runner's rescue clause to be raised only for RuntimeErrors with specific messages?
See https://stackoverflow.com/a/23771227/2981429
If you call raise inside a rescue block, the last raised exception will be re-raised.
In your exception block, you can check the message and choose to re-raise or not:
begin
User.new.charge
rescue RuntimeError => e
case e.message
when "This is a runtime error"
# put your handler code here
else
raise # re-raise the last exception
end
end
However if it's your goal to solely rescue errors that you yourself raise manually, then it's probably easier to define a custom error class instead:
class MyError < StandardError; end
Then instead of raise RuntimeError.new("message") use raise MyError.new("message"), and rescue it normally:
begin
User.new.charge
rescue MyError => e
# handler
end
This way you don't have to worry about your rescues interfering with the built-in exceptions.

Ruby Rspec message expectations for a caught exception

Is there any way with Rspec to set an expectation for an exception that gets caught? I want to verify that MyException gets raised, but since I am catching the exception Rspec does not appear to know it ever happened.
begin
if success
do good stuff
else
raise MyException.new()
end
rescue MyException => e
clean up
end
I've tried a few things like the following without success. MyException.should_receive(:new) and
Kernel.should_receive(:raise).with(MyException)
You could test the behavior of the rescue block instead of checking for the exception:
class Test
def my_method
if success
# do good stuff
else
raise MyException.new()
end
rescue MyException => e
clean_up
end
end
describe Test do
it "should clean up when unsuccessful" do
subject.stub(:success) { false }
subject.should_receive(:clean_up)
subject.my_method
end
end
I figured out how to do what I needed.
class MyClass
def my_method
begin
if success
do good stuff
else
raise MyException.new
end
rescue MyException => e
# clean up
end
end
end
describe MyClass do
it "Expects caught exception" do
my_instance = MyClass.new()
my_instance.should_receive(:raise).with(any_instance_of(MyException))
my_instance.my_method()
end
end
Thanks for your other suggestions.
I would do as below:
RSpec.describe "matching error message with string" do
it "matches the error message" do
expect { raise StandardError, 'this message exactly'}.
to raise_error('this message exactly')
end
end
copied verbatim from Rspec Documentation

How to suppress and not to print the backtrace of the exception on the terminal in ruby using Thor?

Following is my method that might raise the exception.
Its a method of the CLI too that I am building.
Whenever the exception occurs, I want to catch that and just print my custom message on the terminal.
# variation 1
def self.validate(yaml_path)
begin
....
....
rescue
puts "Error"
end
end
# variation 2
def self.validate(yaml_path)
begin
....
....
rescue Exceptino => e
puts "Error: #{e.message}"
end
end
But the backtrace gets printed on the terminal.
How to avoid the backtrace to get printed?
± ../../bin/cf site create
ruby-1.8.7-p352
Error during processing: syntax error on line 52, col 10: ` - label: Price'
/Users/millisami/.rvm/rubies/ruby-1.8.7-p352/lib/ruby/1.8/yaml.rb:133:in `load': syntax error on line 52, col 10: ` - label: Price' (ArgumentError)
.... backtrace .....
.............
The answer was to rescue it on the executable file at bin/<exe>.
Thanks for suggesting
begin
Cf::CLI.start
rescue Psych::SyntaxError
$stderr.puts "\n\tError during processing: #{$!.message}\n\n"
end
The following code doesn't output the backtrace.
class CLS
def hi
begin
raise "X"
rescue
puts $!.message
end
end
end
CLS.new.hi
Have you checked to see if there is another point in the stack where another method is rescuing the exception, outputting the stack trace and then re-raising the exception?
The reason you're not rescuing the exception is because Psych::SyntaxError is not descended from StandardError, so a simple rescue won't catch it. You need to specify a descendant of Psych::SyntaxError:
>> require 'psych'
=> true
>> begin; raise Psych::SyntaxError; rescue; puts "GOT IT"; end
# Psych::SyntaxError: Psych::SyntaxError
# from (irb):8
# from /Users/donovan/.rvm/rubies/ruby-1.9.2-p180/bin/irb:16:in `<main>'
>> Psych::SyntaxError.ancestors
=> [Psych::SyntaxError, SyntaxError, ScriptError, Exception, Object, PP::ObjectMixin, Kernel, BasicObject]
>> begin; raise Psych::SyntaxError; rescue Exception; puts "GOT IT"; end
GOT IT
Notice that in my example rescue Exception does catch it. You should generally be as specific as you can when rescuing unless you really need to rescue all Exceptions. Be aware that suppressing backtraces is good when the exception is something you expect, but if you don't expect it in general it makes debugging much harder.

Resources