Ruby script raising unexpected backtrace - ruby

I have a method that should raise a custom error with a message. When I catch the error and raise my own custom error, it is still raising and printing the backtrace of the original error. I just want the custom error and message. Code below.
Method:
def load(configs)
begin
opts = {access_token: configs['token'],
api_endpoint: configs['endpoint'],
web_endpoint: configs['site'],
auto_paginate: configs['pagination']}
client = Octokit::Client.new(opts)
repos = client.org_repos(configs['org'])
repos.each do |r|
Project.create(name: r.name)
end
rescue Octokit::Unauthorized
raise GitConfigError, "boom"
end
#rescue Octokit::Unauthorized
end
class GitConfigError < StandardError
end
My test (which is failling):
context 'with incorrect git configs' do
before do
allow(loader).to receive(:load).and_raise Octokit::Unauthorized
end
it { expect{loader.load(configs)}.to raise_error(GitConfigError, "boom" ) }
end
Test Output:
GitProjectLoader#load with incorrect git configs should raise GitConfigError with "boom"
Failure/Error: it { expect{loader.load(configs)}.to raise_error(GitConfigError, "boom" ) }
expected GitConfigError with "boom", got #<Octokit::Unauthorized: Octokit::Unauthorized> with backtrace:
# ./spec/lib/git_project_loader_spec.rb:24:in `block (5 levels) in <top (required)>'
# ./spec/lib/git_project_loader_spec.rb:24:in `block (4 levels) in <top (required)>'
# ./spec/lib/git_project_loader_spec.rb:24:in `block (4 levels) in <top (required)>'

If you intend to test the handling of the Octokit::Unauthorized error, then raise the error anywhere before the rescue kicks in. Preferably, someplace where it would actually be raised.
Something like this, for example:
before do
allow(Octokit::Client).to receive(:new).and_raise(Octokit::Unauthorized)
end
And then:
expect{ loader.load(configs) }.to raise_error(GitConfigError, "boom" )
As a side note, I would discourage enclosing all lines of your method in a begin;rescue;end structure; you should enclose only the lines from which you are expecting errors.

You are not testing your code as you think. You have mocked it out.
The line
allow(loader).to receive(:load).and_raise Octokit::Unauthorized
replaces the load method on loader with a stub which just raises the named error.
Remove your before block, and it should test your code as intended. Note as written it will make a real request via Octokit, unless you mock that out instead.

Related

RSpec: Expecting method to raise an error fails

I'm trying to test that an error is raised correctly under certain conditions. In this spec, the error is raised, but the test still fails. What am I doing wrong?
require 'spec_helper'
describe USBTeensyRenderer do
context 'when the correct USB port name is not present' do
it 'raises an error on instantiation' do
expect(renderer = USBTeensyRenderer.new).to raise_error(USBInitError)
end
end
end
And the terminal output of 'bundle exec rspec':
Failures:
1) USBTeensyRenderer when the correct USB port name is not present raises an error on instantiation
Failure/Error: expect(renderer = USBTeensyRenderer.new).to raise_error(USBInitError)
USBInitError:
USB output couldn't be initialized
# ./lib/ivan/view/renderers/usb_teensy_renderer.rb:9:in `rescue in initialize'
# ./lib/ivan/view/renderers/usb_teensy_renderer.rb:6:in `initialize'
# ./spec/models/usb_teensy_renderer_spec.rb:10:in `new'
# ./spec/models/usb_teensy_renderer_spec.rb:10:in `block (3 levels) in <top (required)>'
Finished in 0.00351 seconds (files took 0.11638 seconds to load)
8 examples, 1 failure
Failed examples:
rspec ./spec/models/usb_teensy_renderer_spec.rb:9 # USBTeensyRenderer when the correct USB port name is not present raises an error on instantiation
Here's how the error is raised in the class:
def initialize
begin
#sp = SerialPort.new("/dev/tty.usbmodem54121", 9600, 8, 1)
rescue
raise USBInitError, "USB output couldn't be initialized"
end
#sp.get_modem_params()
end
I believe expect should take a block in this case:
expect { renderer = USBTeensyRenderer.new }.to raise_error(USBInitError)
This thread has a pretty good explanation on expect() vs expect {}
Rspec: expect vs expect with block - what's the difference?

Rspec raise_error within context doesn't seem to work

I had this written, and it passed.
it 'raises a GitConfigNotFound error when YAML config file cannot be found' do
allow(YAML).to receive(:load_file)
.with(Rails.root.join('config', 'git_config.yml'))
.and_raise(Errno::ENOENT)
expect { described_class::config }.to raise_error GitConfigNotFound
end
Then I tried to put it within a context to match my other tests and it failed. I formatted as shown below. Does anybody have any insight as to why this is happening?
context 'will raise a GitConfigNotFound exception if git config file is missing' do
before do
allow(YAML).to receive(:load_file)
.with(Rails.root.join('config', 'git_config.yml'))
.and_raise(Errno::ENOENT)
end
it { expect(described_class::config).to raise_error GitConfigNotFound }
end
It is giving me this output, which seems to be what I want but for some reason doesn't catch it.:
1) GitConfigsLoader will raise a GitConfigNotFound exception if git config file is missing
Failure/Error: it { expect(described_class::config).to raise_error }
GitConfigNotFound:
Error: git_config.yml not found.
# ./lib/git_configs_loader.rb:9:in `rescue in config'
# ./lib/git_configs_loader.rb:7:in `config'
# ./spec/lib/git_configs_loader_spec.rb:37:in `block (3 levels) in <top (required)>'
Perhaps this is what #PeterAlfvin meant, but I finally foud the answer per another one of his answers! I was using expect(...) rather than expect{...}. The parens executes immediately and blows up instantly and isn't caught by the .to raise_exception. Using the braces allows for the raise_error to execute the except block and catch the error.
context 'when no git_config.yml file is proivded' do
before do
allow(YAML).to receive(:load_file).and_raise(Errno::ENOENT)
end
it { expect{ described_class::config }.to raise_exception GitConfigNotFound }
end

RSpec hits an error in code, before it is able to expect an error to be raised

This is something that I've seen before when using RSpec Rails and I believe that I know what is happening, I just don't know how I can get around it.
To me, it appears that the following test should pass. It expects an error, and an error is raised although I assume that the source of the error is what it is tripping up on.
csv_file_spec.rb
require 'spec_helper'
RSpec.describe Cleaner::CSVFile do
context 'when CSV file does not exist' do
let(:file) { Cleaner::CSVFile.new('tmp/file-does-not-exist.csv') }
it 'raises error' do
expect(file).to raise_error
end
end
end
csv_file.rb
module Cleaner
# A CSVFile is a CSV file loaded into memory. It exposes the clean method.
class CSVFile
attr_accessor :raw
def initialize(file)
#raw = File.open(file)
end
end
end
Output
1) Cleaner::CSVFile is not valid
Failure/Error: expect(Cleaner::CSVFile.new('tmp/file-does-not-exist.csv')).to raise_error
Errno::ENOENT:
No such file or directory # rb_sysopen - tmp/file-does-not-exist.csv
# ./lib/cleaner/csv_file.rb:8:in `initialize'
# ./lib/cleaner/csv_file.rb:8:in `open'
# ./lib/cleaner/csv_file.rb:8:in `initialize'
# ./spec/csv_file_spec.rb:7:in `new'
# ./spec/csv_file_spec.rb:7:in `block (2 levels) in <top (required)>'
I can see that the CSVFile object is not able to be initialized because the file does not exist and that'll be why RSpesc can't continue the test but what can I do to get around this?
I get the feeling that there is something fundamentally wrong with my approach to testing that I'm not seeing. I'd rather delegate the error to the standard File class, and not raise my own error messages as the error is verbose enough and I'd only be duplicating effort - should I be implementing my own instead?
Thanks!
For exceptions you should use block or lambda in expect syntax:
it 'raises error' do
expect{ Cleaner::CSVFile.new('tmp/file-not-exist.csv') }.to raise_error
end
You could use stubbing also :
require 'spec_helper'
RSpec.describe Cleaner::CSVFile do
context 'when CSV file does not exist' do
it 'raises error' do
allow(described_class).to receive(:new).and_raise("File not exist")
expect { described_class.new }.to raise_error("File not exist")
end
end
end
Read match message with a string.

Pure Ruby rspec test passes without method being defined

I have an rspec test on a pure Ruby model:
require 'spec_helper'
require 'organization'
describe Organization do
context '#is_root?' do
it "creates a root organization" do
org = Organization.new
expect { org.is_root?.to eq true }
end
end
end
My organization model looks like this:
class Organization
attr_accessor :parent
def initialize(parent = nil)
self.parent = parent
end
end
The output when running the tests:
bundle exec rspec spec/organization_spec.rb:6
Run options: include {:locations=>{"./spec/organization_spec.rb"=>[6]}}
.
Finished in 0.00051 seconds
1 example, 0 failures
When I run the test, it passes, despite the fact that the method is_root? doesn't exist on the model. I usually work in Rails, not pure Ruby, and I've never seen this happen. What is going on?
Thanks!
It should be:
expect(org.is_root?).to eq true
When you pass block to expect it is being wrapped in ExpectationTarget class (strictly speaking BlockExpectationTarget < ExpectationTarget). Since you didn't specify what you expect from this object, the block is never executed, hence no error is raised.
You are passing a block to expect, which is never being called. You can see this by setting an expectation on that block
expect { org.is_root?.to eq true }.to_not raise_error
1) Organization#is_root? creates a root organization
Failure/Error: expect { puts "HI";org.is_root?.to eq true }.to_not raise_error
expected no Exception, got #<NoMethodError: undefined method `is_root?' for #<Organization:0x007ffa798c2ed8 #parent=nil>> with backtrace:
# ./test_spec.rb:15:in `block (4 levels) in <top (required)>'
# ./test_spec.rb:15:in `block (3 levels) in <top (required)>'
# ./test_spec.rb:15:in `block (3 levels) in <top (required)>'
Or by just putting a plain raise or puts inside the block, neither of which will be called:
expect { puts "HI"; raise; org.is_root?.to eq true }
The block form is used for expecting that a piece of code raises an exception or not. The correct syntax for checking values is:
expect(org.is_root?).to eq(true)

Ruby NameError undefined local variable or method `e

class TwitterProfile < ActiveRecord::Base
def send_status_update(status_update)
if publish?
client = Twitter::Client.new( :oauth_token => authentication.token,
:oauth_token_secret => authentication.secret)
client.update(status_update.to_twitter_string)
end
rescue Exception => e
logger.info "Error publishing to twitter: #{e.to_s}"
end
end
There is a StatusUpdate model and an observer that reposts them to Twitter in after_create. I sometimes get the following exception:
NameError (undefined local variable or method `e' for #<TwitterProfile:0x00000004e44ab8>):
app/models/twitter_profile.rb:23:in `rescue in send_status_update'
app/models/twitter_profile.rb:18:in `send_status_update'
app/models/status_update_observer.rb:6:in `block in after_create'
app/models/status_update_observer.rb:4:in `after_create'
app/models/workout_observer.rb:5:in `after_update'
app/controllers/frames_controller.rb:76:in `update'
app/controllers/application_controller.rb:24:in `call'
app/controllers/application_controller.rb:24:in `block (2 levels) in <class:ApplicationController>'
app/controllers/application_controller.rb:10:in `block in <class:ApplicationController>'
What am I missing here?
I have one thing I know and one that's just a wild guess.
The thing I know is that you don't need to call to_s on an overall #{} expression; that will happen automatically. But it does no harm.
My wild guess is that your test case is not really running the code you have posted. What happens if you change e to f?
I should note that rescuing Exception itself is usually a bad idea. You should rescue RuntimeError or StandardError at the highest, and preferably something more specific. You can get fairly strange errors when rescuing Exception because you interfere with threads and interpreter-level events.
You're missing the 'begin' block of the begin/rescue clause.

Resources