How to use RSpec to test that a method catches a symbol? - ruby

How can you test that a method catches a thrown symbol in RSpec? I have two methods that interact with each other through #throw and #catch. I already figured out how to test that the symbol is thrown on one end:
expect { subject.method_a }.to throw_symbol(:some_symbol)
Now I want to test that method_b catches the thrown symbol, which I imagine might look something like this:
expect { subject.method_b }.to catch_symbol(:some_symbol)
Only that doesn't work. So my question is, how can you test that a method catches a symbol in RSpec?
EDIT: Here's a very basic example of method_a and method_b, stripped of all logic not related to the problem at hand.
def method_a
throw :some_symbol
end
def method_b
catch :some_symbol do
method_a
end
end

catch and throw are methods on Kernel so you can expect them as usual:
class SomeClass
def a
throw :foo
end
def b
catch :foo do
a
end
end
end
RSpec.describe "" do
it "" do
inst = SomeClass.new
expect(inst).to receive(:throw).with(:foo)
inst.a
expect(inst).to receive(:catch).with(:foo)
inst.b
end
end

Related

Rescue exceptions for all methods in a class

Basically, this is normal code:
class Foo
def hi
# your code here....
rescue => e
# Raise error here
end
def hello
# your code here...
rescue => e
# Raise error here
end
end
But in PHP, i can use __call magic method to create abstract class, look like this:
class FooAbstract {
public function __call($name, $args) {
# Try catch in here...
}
}
class Foo extends FooAbstract {
public function hi() {
# Code with try catch...
}
}
How can i use __call method in Ruby class???
You could define a module that when included defines a method_added hook that wraps all new methods inside a begin..rescue block:
require 'set'
module ExceptionHandler
def self.included(klass)
super
klass.send(:extend, ClassMethods)
end
module ClassMethods
def exception_handler(&block)
#__exception_handler = block.to_proc
end
def handle_exception(exception)
defined?(#__exception_handler) ? #__exception_handler.call(exception) : super
end
def handle_method_exceptions(method_name)
old_method = instance_method(method_name)
return if (#__hooked_methods ||= Set.new).include?(method_name)
#__ignoring_added_methods = true # avoid infinite define_method/method_added loop
define_method method_name do |*args, &block|
begin
old_method.bind(self).(*args, &block)
rescue => ex
self.class.handle_exception(ex)
end
end
#__ignoring_added_methods = false
#__hooked_methods << method_name
end
def method_added(method_name)
super
unless #__ignoring_added_methods
handle_method_exceptions(method_name)
end
end
end
end
This would be used like:
class Foo
include ExceptionHandler
exception_handler do |exception|
puts "Catched an exception:"
puts "---------------------"
puts "Exception class: #{exception.class}"
puts "Message: #{exception.message}"
puts "Backtrace:"
puts exception.backtrace.join("\n ")
puts
puts "reraising.."
puts
raise exception
end
def this_raises
raise "somebody set up us the bomb"
end
end
Foo.new.this_raises
This would output:
Catched an exception:
---------------------
Exception class: RuntimeError
Message: somebody set up us the bomb
Backtrace:
errorhandler.rb:62:in `this_raises'
errorhandler.rb:26:in `call'
errorhandler.rb:26:in `block in handle_exceptions'
errorhandler.rb:67:in `<main>'
reraising..
I'm not sure if it is a good idea.
You could take out the method_added part and it would look something like:
class Foo
with_rescue def foofoo(arg)
puts arg.inspect
end
end
(You can just rename the handle_method_exceptions to with_rescue and remove all the #__ignoring_added_methods trickery and the method_added method and it should work as described).
I'm not sure what you want to achieve here, but the Ruby equivalent of PHP's __call() is method_missing.
By default, when you try to call a non-existing method you will get an exception. But if you want to implement an "abstract class". You could also try this solution: https://stackoverflow.com/a/512505/185870

Preventing error from causing RSpec test failure

Suppose I have a class with methods like these:
class MyClass
...
def self.some_class_method
my_instance = MyClass.new
self.other_class_method(my_instance)
raise 'ERROR'
end
def self.other_class_method(instance)
...
end
end
And the test for it looks like this:
require 'spec_helper'
describe MyClass do
describe '.some_class_method' do
context 'testing some_class_method' do
it 'calls other_class_method' do
MyClass.should_receive(:other_class_method)
MyClass.some_class_method
end
end
end
end
The test errors out with ERROR, and if I remove the raise 'ERROR' line, the test passes. But here I want to only test whether some_class_method calls other_class_method, regardless of what happens afterwards. I could change it to expect the method to raise an error, but that's not the purpose of this particular test. Is there a better way?
You could rescue the exception in the test.
describe MyClass do
describe '.some_class_method' do
context 'testing some_class_method' do
it 'calls other_class_method' do
MyClass.should_receive(:other_class_method)
begin
MyClass.some_class_method
rescue
end
end
end
end
end
What about adding an expectation that the method is raising an error. That will even enhance your testing:
describe MyClass do
describe '.some_class_method' do
context 'testing some_class_method' do
it 'calls other_class_method' do
expect(MyClass).to receive(:other_class_method)
expect { MyClass.some_class_method }.to raise_error("ERROR")
end
end
end
end

Yielding a block to a proc (or creating a method that accepts a block from a proc that yields)

I'm currently working on an interface that allows me to wrap arbitrary method calls with a chain of procs. Without going into too much detail, I currently have an interface that accepts something like this:
class Spy
def initialize
#procs = []
end
def wrap(&block)
#procs << block
end
def execute
original_proc = Proc.new { call_original }
#procs.reduce(original_proc) do |memo, p|
Proc.new { p.call &memo }
end.call
end
def call_original
puts 'in the middle'
end
end
spy = Spy.new
spy.wrap do |&block|
puts 'hello'
block.call
end
spy.wrap do |&block|
block.call
puts 'goodbye'
end
spy.execute
What I'd like to do though is remove the |&block| and block.call from my API and use yield instead.
spy.wrap do
puts 'hello'
yield
end
This didn't work and raised a LocalJumpError: no block given (yield) error.
I've also tried creating methods by passing the proc the define_singleton_method in the reduce, but I haven't had any luck.
def execute
original_proc = Proc.new { call_original }
#procs.reduce(original_proc) do |memo, p|
define_singleton_method :hello, &p
Proc.new { singleton_method(:hello).call(&memo) }
end.call
end
Is there another approach I can use? Is there anyway to yield from a Proc or use the Proc to initialize something that can be yielded to?
Using yield in your wrap block does not make much sense unless you passed a block to the caller itself:
def foo
spy.wrap do
puts "executed in wrap from foo"
yield
end
end
If you call foo without a block it will raise the exception since yield can't find a block to execute. But if you pass a block to foo method then it will be invoked:
foo do
puts "foo block"
end
Will output
executed in wrap from foo
foo block
In conclusion I think you misunderstood how yield works and I don't think it is what you want to achieve here.

I created a DSL to define factories in ruby. Is there a better way to do it?

I have ruby application and I want to implement a DSL to define Factories. Factories are classes that instantiates some object, execute some logic in the process, perform some validations and execute some callbacks depending on the results (succeeded or failed):
f = Factory.new
f.create(foo: :bar) do |on|
on.success { puts 'hey from success action callback' }
on.failure { puts 'hey from failure action callback' }
end
Ok, this is not that hard to do, but I also want to stop the create method right after the method fails or succeeds, something like:
def create(options = {})
# some logic
failed! # this method stops execution and yields the callback object
puts "you'll never see this"
end
What I came up with is this: https://gist.github.com/esdras/631a04769f24856c6d7f
See a partial version below:
require 'fiber'
class Factory
class Callbacks
# omitted some code here, this class is basically a container for
# success and failure callbacks
end
def failed!
#callbacks.failed!
resume_context
end
def succeeded!
#callbacks.succeeded!
resume_context
end
def resume_context ; Fiber.yield ; end
def self.handle(name, &method_body)
define_method "__original_#{name}__", &method_body
define_method name do |*args, &block|
#callbacks = Callbacks.new(self, block)
Fiber.new { send("__original_#{name}__", *args) }.resume
#callbacks
end
end
handle :create do |options = {}|
puts options.inspect
puts "in create"
succeeded!
puts 'after succeeded, never reached here'
end
end
As you can see the class method handle defines two methods: __original_create__ and create which wraps __original_create__ in a Fiber to make it possible to stop execution immediately and execute the callbacks. My question is: Is there a better way to do this? Without creating that __original_create__ method or even without using Fibers?
I already tried this:
def self.handle(name, &method_body)
define_method name do |*args, &block|
#callbacks = Callbacks.new(self, block)
Fiber.new { method_body.call *args }.resume
# above method_body is evaluated in the context of the class.
#callbacks
end
end
but method_body is evaluated in the context of the class, not the instance:
I also tried to instance_eval the method_body like this:
def self.handle(name, &method_body)
define_method name do |*args, &block|
#callbacks = Callbacks.new(self, block)
Fiber.new { instance_eval &method_body }.resume
# above we lost the parameters defined by the handle method
#callbacks
end
end
but I lost the reference to the parameters defined by:
handle :create do |param1, param2|
# method body
end
The only way I found is defining a method with the block passed to the handle method and after defining a wrapper method that calls the original method, like I did above with __original_create__. I'm not ok with defining an extra method, there got to be another way to do this. :(
Any insights would be appreciated.
I'm not sure what you need the Fiber for, so I will leave it, but you need is instance_exec
def self.handle(name, &method_body)
define_method name do |*args, &block|
#callbacks = Callbacks.new(self, block)
Fiber.new { instance_exec *args, &method_body }.resume
# above we lost the parameters defined by the handle method
#callbacks
end
end
One way to do it is to use throw and catch.
def create(options = {})
case catch(:result) do
throw :result, :success if ...
throw :result, :error if ...
end
when :success then ...
when :error then ...
end
end

Is there a way to avoid definition of a method with a certain name?

Is there a way to avoid (raise an error when it is attempted) definition of a method with a certain name, for example Foo#bar? (A usecase would be when Foo#bar is already defined, and I want to avoid that method being overridden, but that is irrelevant to the question.) I am assuming something like:
class Foo
prohibit_definition :bar
end
...
# Later in some code
class Foo
def bar
...
end
end
# => Error: `Foo#bar' cannot be defined
class Class
def method_added(method_name)
raise "So sad, you can't add" if method_name == :bad
end
end
class Foo
def bad
puts "Oh yeah!"
end
end
#irb> RuntimeError: So sad, you can't add
# from (irb):3:in `method_added'
# from (irb):7
Maybe you can catch the callback in method_added() of Module class and check the method name and delete the added method if it does not meet your criteria. Then you can raise an error.
Not you want exactly but close enough I think.
class Class
def method_added(method_name)
if method_name == :bar
remove_method :bar
puts "#{method_name} cannot be added to #{self}"
end
end

Resources