Rspec - How to stub a 3rd party exception - ruby

I'm trying to test that I'm able to capture these AWS exceptions:
begin
s3_client = S3Client.new
s3_file = s3_client.write_s3_file(bucket, file_name, file_contents)
rescue AWS::Errors::ServerError, AWS::Errors::ClientError => e
# do something
end
My Rspec 3 code:
expect_any_instance_of(S3Client).to receive(:write_s3_file).and_raise(AWS::Errors::ServerError)
But when I test this stub, I get a TypeError:
exception class/object expected
Do I have to include AWS::Errors::ServerError? If so, how would I do that? I'm using the aws-sdk-v1 gem.
Thanks.

I would build in a port, and then inject a stubbed out object that is just dying to throw you an error. Let me explain:
class ImgService
def set_client(client=S3Client.new)
#client = client
end
def client
#client ||= S3Client.new
end
def write(bucket, file_name, file_contents)
begin
#client.write_s3_file(bucket, file_name, file_contents)
rescue AWS::Errors::ServerError, AWS::Errors::ClientError => e
# do something
end
end
end
test:
describe "rescuing an AWS::Error" do
before :each do
#fake_client = double("fake client")
allow(#fake_client).to receive(:write_s3_file).and_raise(AWS::Errors::ServerError)
#img_service = ImgService.new
#img_service.set_client(#fake_client)
end
# ...
end

Without having to require the specific files with those exceptions, you can stub the exceptions in your spec file:
stub_const("AWS::Errors::ServerError", StandardError)
stub_const("AWS::Errors::ClientError", StandardError)
Then your expect will work.
This also works well for testing Rails exceptions such as ActiveRecord::RecordNotUnique.

Related

Best practice of error handling on controller and interactor

# users_show_controller.rb
class Controllers::Users::Show
include Hanami::Action
params do
required(:id).filled(:str?)
end
def call(params)
result = users_show_interactor(id: params[:id])
halt 404 if result.failure?
#user = result.user
end
end
# users_show_interactor.rb
class Users::Show::Interactor
include Hanami::Interactor
expose :user
def call(:id)
#user = UserRepository.find_by(:id)
end
end
I have a controller and a interactor like above.
And I'm considering the better way to distinguish ClientError from ServerError, on the controller.
I think It is nice if I could handle an error like below.
handle_exeption StandardError => :some_handler
But, hanami-interactor wraps errors raised inside themselves and so, controller receive errors through result object from interactor.
I don't think that re-raising an error on the controller is good way.
result = some_interactor.call(params)
raise result.error if result.failure
How about implementing the error handler like this?
I know the if statement will increase easily and so this way is not smart.
def call(params)
result = some_interactor.call(params)
handle_error(result.error) if result.faulure?
end
private
def handle_error(error)
return handle_client_error(error) if error.is_a?(ClientError)
return server_error(error) if error.is_a?(ServerError)
end
Not actually hanami-oriented way, but please have a look at dry-monads with do notation. The basic idea is that you can write the interactor-like processing code in the following way
def some_action
value_1 = yield step_1
value_2 = yield step_2(value_1)
return yield(step_3(value_2))
end
def step_1
if condition
Success(some_value)
else
Failure(:some_error_code)
end
end
def step_2
if condition
Success(some_value)
else
Failure(:some_error_code_2)
end
end
Then in the controller you can match the failures using dry-matcher:
matcher.(result) do |m|
m.success do |v|
# ok
end
m.failure :some_error_code do |v|
halt 400
end
m.failure :some_error_2 do |v|
halt 422
end
end
The matcher may be defined in the prepend code for all controllers, so it's easy to remove the code duplication.
Hanami way is validating input parameters before each request handler. So, ClientError must be identified always before actions logic.
halt 400 unless params.valid? #halt ClientError
#your code
result = users_show_interactor(id: params[:id])
halt 422 if result.failure? #ServerError
halt 404 unless result.user
#user = result.user
I normally go about by raising scoped errors in the interactor, then the controller only has to rescue the errors raised by the interactor and return the appropriate status response.
Interactor:
module Users
class Delete
include Tnt::Interactor
class UserNotFoundError < ApplicationError; end
def call(report_id)
deleted = UserRepository.new.delete(report_id)
fail_with!(UserNotFoundError) unless deleted
end
end
end
Controller:
module Api::Controllers::Users
class Destroy
include Api::Action
include Api::Halt
params do
required(:id).filled(:str?, :uuid?)
end
def call(params)
halt 422 unless params.valid?
Users::Delete.new.call(params[:id])
rescue Users::Delete::UserNotFoundError => e
halt_with_status_and_error(404, e)
end
end
end
fail_with! and halt_with_status_and_error are helper methods common to my interactors and controllers, respectively.
# module Api::Halt
def halt_with_status_and_error(status, error = ApplicationError)
halt status, JSON.generate(
errors: [{ key: error.key, message: error.message }],
)
end
# module Tnt::Interactor
def fail_with!(exception)
#__result.fail!
raise exception
end

API integration error HTTParty

I'm learning how to work with HTTParty and API and I'm having an issue with my code.
Users/admin/.rbenv/versions/2.0.0-p481/lib/ruby/2.0.0/uri/generic.rb:214:in `initialize': the scheme http does not accept registry part: :80 (or bad hostname?)
I've tried using debug_output STDOUT both as an argument to my method and after including HTTParty to have a clue but with no success. Nothing gets displayed:
require 'httparty'
class LolObserver
include HTTParty
default_timeout(1) #timeout after 1 second
attr_reader :api_key, :playerid
attr_accessor :region
def initialize(region,playerid,apikey)
#region = region_server(region)
#playerid = playerid
#api_key = apikey
end
def region_server(region)
case region
when "euw"
self.class.base_uri "https://euw.api.pvp.net"
self.region = "EUW1"
when "na"
self.class.base_uri "https://na.api.pvp.net"
self.region = "NA1"
end
end
def handle_timeouts
begin
yield
#Timeout::Error, is raised if a chunk of the response cannot be read within the read_timeout.
#Timeout::Error, is raised if a connection cannot be created within the open_timeout.
rescue Net::OpenTimeout, Net::ReadTimeout
#todo
end
end
def base_path
"/observer-mode/rest/consumer/getSpectatorGameInfo"
end
def current_game_info
handle_timeouts do
url = "#{ base_path }/#{region}/#{playerid}?api_key=#{api_key}"
puts '------------------------------'
puts url
HTTParty.get(url,:debug_output => $stdout)
end
end
end
I verified my URL which is fine so I'm lost as to where the problem is coming from.
I tested with a static base_uri and it doesn't change anything.
The odd thing is when I do:
HTTParty.get("https://euw.api.pvp.net/observer-mode/rest/consumer/getSpectatorGameInfo/EUW1/randomid?api_key=myapikey")
Everything is working fine and I'm getting a response.
HTTParty doesn't seem to like the way you set your base_uri.
Unless you need it to be like that just add another attr_reader called domain and it will work.
require 'httparty'
class LolObserver
include HTTParty
default_timeout(1) #timeout after 1 second
attr_reader :api_key, :playerid, :domain
attr_accessor :region
def initialize(region,playerid,apikey)
#region = region_server(region)
#playerid = playerid
#api_key = apikey
end
def region_server(region)
case region
when "euw"
#domain = "https://euw.api.pvp.net"
self.region = "EUW1"
when "na"
#domain = "https://na.api.pvp.net"
self.region = "NA1"
end
end
def handle_timeouts
begin
yield
#Timeout::Error, is raised if a chunk of the response cannot be read within the read_timeout.
#Timeout::Error, is raised if a connection cannot be created within the open_timeout.
rescue Net::OpenTimeout, Net::ReadTimeout
#todo
end
end
def base_path
"/observer-mode/rest/consumer/getSpectatorGameInfo"
end
def current_game_info
handle_timeouts do
url = "#{domain}/#{ base_path }/#{region}/#{playerid}?api_key=#{api_key}"
puts '------------------------------'
puts url
HTTParty.get(url,:debug_output => $stdout)
end
end
end

How to test that a block is called within a thread?

I am working on wrapping the ruby-mqtt gem into a class which implements a subscribe and publish method. The subscribe method connects to the server and listens in a separate thread because this call is synchronous.
module PubSub
class MQTT
attr_accessor :host, :port, :username, :password
def initialize(params = {})
params.each do |attr, value|
self.public_send("#{attr}=", value)
end if params
super()
end
def connection_options
{
remote_host: self.host,
remote_port: self.port,
username: self.username,
password: self.password,
}
end
def subscribe(name, &block)
channel = name
connect_opts = connection_options
code_block = block
::Thread.new do
::MQTT::Client.connect(connect_opts) do |c|
c.get(channel) do |topic, message|
puts "channel: #{topic} data: #{message.inspect}"
code_block.call topic, message
end
end
end
end
def publish(channel = nil, data)
::MQTT::Client.connect(connection_options) do |c|
c.publish(channel, data)
end
end
end
end
I have a test that I have written using rspec to test the class but it does not pass.
mqtt = ::PubSub::MQTT.new({host: "localhost",port: 1883})
block = lambda { |channel, data| puts "channel: #{channel} data: #{data.inspect}"}
block.should_receive(:call).with("channel", {"some" => "data"})
thr = mqtt.subscribe("channel", &block)
mqtt.publish("channel", {"some" => "data"})
When I run the following ruby-mqtt-example I have now problems at all.
uri = URI.parse ENV['CLOUDMQTT_URL'] || 'mqtt://localhost:1883'
conn_opts = {
remote_host: uri.host,
remote_port: uri.port,
username: uri.user,
password: uri.password,
}
# Subscribe example
Thread.new do
puts conn_opts
MQTT::Client.connect(conn_opts) do |c|
# The block will be called when you messages arrive to the topic
c.get('test') do |topic, message|
puts "#{topic}: #{message}"
end
end
end
# Publish example
puts conn_opts
MQTT::Client.connect(conn_opts) do |c|
# publish a message to the topic 'test'
loop do
c.publish('test', 'Hello World')
sleep 1
end
end
So my question is, what am I doing wrong when I simply create a class and separate out the publish and subscribe logic? My guess is that it has something to do with Threading in the function call but I can't seem to figure it out. Any help is much appreciated.
UPDATE
I believe I know why the test is not passing and it is because when I pass a lambda in to subscribe expecting it to receive a call it actually will not receive the call when it exits the method or until publish is called. So I would like to rephrase the question to: How do I test that a block is called within a thread? If someone answers, "you don't", then the question is: How do you test that block is being called in an infinite loop like in the example of calling get within ruby-mqtt gem.
The RSpec expectations machinery will work fine with threads, as evidenced by the following example, which passes:
def foo(&block)
block.call(42)
end
describe "" do
it "" do
l = lambda {}
expect(l).to receive(:call).with(42)
Thread.new { foo(&l) }.join
end
end
The join waits for the thread(s) to finish before going further.

Collecting exceptions in ruby script

I'm writing a script which collects data from various url's. I want to collect errors from begin rescue blocks into an array to output them when the program runs in verbose mode. With normal use, a failed connection is ignored and the script moves on to the next url.
I thought the best way to do this would be to create an array errArray = Array.new at the top of the script to hold errors, and then do:
rescue Exception => e
errArray << e.message
in various functions to log errors. The die function outputs the array using p unless it is empty. However, I get the error
Undefined local variable or method 'errArray'
Any help (and constructive criticism) appreciated.
EDIT: die function:
def die(e)
p errorArray unless errorArray.empty?
# Some other irrelevant code
end
errArray is not global variable and therefore methods have no access to it. You can declare it as a global variable by $err_array.
However the best solution would be create a simple class:
class ExceptionCollector
def collect
yield
rescue => e
errors << e.message
end
def errors
#errors ||= []
end
end
And then simple:
$logger = ExceptionCollector.new
$logger.collect do
# this may raise an exception
end
def foo
$logger.collect do
# another exception
end
end
$logger.errors #=> list of errors

How to rescue all exceptions under a certain namespace?

Is there a way to rescue all exceptions under a certain namespace?
For example, I want to rescue all of the Errno::* exceptions (Errno::ECONNRESET, Errno::ETIMEDOUT). I can go ahead and list them all out on my exception line, but I was wondering if I can do something like.
begin
# my code
rescue Errno
# handle exception
end
The above idea doesn't seem to work, thus is there something similar that can work?
All the Errno exceptions subclass SystemCallError:
Module Errno is created dynamically to map these operating system errors to Ruby classes, with each error number generating its own subclass of SystemCallError. As the subclass is created in module Errno, its name will start Errno::.
So you could trap SystemCallError and then do a simple name check:
rescue SystemCallError => e
raise e if(e.class.name.start_with?('Errno::'))
# do your thing...
end
Here is another interesting alternative. Can be adapted to what you want.
Pasting most interesting part:
def match_message(regexp)
lambda{ |error| regexp === error.message }
end
begin
raise StandardError, "Error message about a socket."
rescue match_message(/socket/) => error
puts "Error #{error} matches /socket/; ignored."
end
See the original site for ruby 1.8.7 solution.
It turns out lambda not accepted my more recent ruby versions. It seems the option is to use what worked in 1.8.7 but that's IM slower (to create a new class in all comparisons. So I don't recommend using it and have not even tried it:
def exceptions_matching(&block)
Class.new do
def self.===(other)
#block.call(other)
end
end.tap do |c|
c.instance_variable_set(:#block, block)
end
end
begin
raise "FOOBAR: We're all doomed!"
rescue exceptions_matching { |e| e.message =~ /^FOOBAR/ }
puts "rescued!"
end
If somebody knows when ruby removed lambda support in rescue please comment.
All classes under Errno are subclasses of SystemCallError. And all subclasses of SystemCallError are classes under Errno. The 2 sets are identical, so just rescue SystemCallError. This assumes that you're not using an external lib that adds to one and not the other.
Verify the identity of the 2 sets (using active_support):
Errno.constants.map {|name|
Errno.const_get(name)
}.select{|const|
Class === const
}.uniq.map(&:to_s).sort ==
SystemCallError.subclasses.map(&:to_s).sort
This returns true for me.
So, applied to your example:
begin
# my code
rescue SystemCallError
# handle exception
end
Here is a more generic solution, in the case you wanted to rescue some Errno types and not others.
Create a custom module to be included by all the error classes we want to rescue
module MyErrnoModule; end
Customize this array to your liking, up to the "each" call.
Errno.constants.map {|name|
Errno.const_get(name)
}.select{|const|
Class === const
}.uniq.each {|klass|
klass.class_eval {
include MyErrnoModule
}
}
Test:
begin
raise Errno::EPERM
rescue MyErrnoModule
p "rescued #{$!.inspect}"
end
Test result:
"rescued #<Errno::EPERM: Operation not permitted>"
I would guess this performs slightly better than a solution that needs to check the name of the exception.

Resources