rspec yield block, but call original - ruby

So I have the following:
foo.each do |f|
f.begin
do_stuff
do_more_stuff
end
end
And I mock the f object with an and_yield() call. I want to be able to test the begin method by passing it the original block { do_stuff do_more_stuff }, not a mock implementation.... I cant just let the begin method be called on the mock without at least stubbing it, so what do I do?

Again, an undocumented feature that i found:
allow(thing).to receive(:foo) do |_, &block|
block.call
end
le sigh....

The following worked for me:
original = thing.method(:foo)
expect(thing).to receive(:foo) do |_params|
# check params
expect(params).to include(something)
# then
original.call(params)
end

Related

Can an I make this "block wrapping" optional in a idiomatic/neat way?

In ruby, you can call a remote api like this
def get_remote_date
Net::HTTP.get('example.com', '/index.json')
end
If you do gem install vcr, you can do this
def get_remote_date
VCR.use_cassette("cassette_001") do
Net::HTTP.get('example.com', '/index.json')
end
end
Vcr recording/playback helps during development, when
the remote api is expensive.
Whether to use vcr or not should be optional, indicated
by the first argument of the function:
def get_remote_date(should_use_vcr)
VCR.use_cassette("cassette_001") do
Net::HTTP.get('example.com', '/index.json')
end
end
My question is, how do I rewrite the method so that the "block wrapping" / "VCR.use_cassette("cassette_001") do" is conditional on the boolean value of the should_use_vcr local variable.
I could do
def get_remote_date(should_use_vcr)
if conditional here
VCR.use_cassette("cassette_001") do
Net::HTTP.get('example.com', '/index.json')
end
else
Net::HTTP.get('example.com', '/index.json')
end
end
but for a complex method that has "Net::HTTP.get( " plus a dozen more lines, there's too much duplication of code,
so looking for a neater way to do it.
You can try to use the following approach:
def get_remote_date
record_request { Net::HTTP.get('example.com', '/index.json') }
end
def record_request(&request)
ENV['RECORD_REQUEST'] ? VCR.use_cassette("cassette_001", &request) : request.call
end
This is a nice article, which explains what does &block (ampersand parameter) mean and how it relates to yield keyword.
Illustrating a yield based solution,
def maybe_cache_http_requests(cassette)
if ENV['CACHE_HTTP_REQUESTS'] == "1"
require 'vcr'
VCR.configure do |config|
config.cassette_library_dir = "vcr_cassettes"
config.hook_into :webmock
end
VCR.use_cassette(cassette) do
yield
end
else
yield
end
end
You could put the duplicate code into a method and call that method either wrapped in the VCR do block or without VCR.

How to test ruby module methods with block using Rspec?

I want to test a following method, which calls a module method with a block.
def test_target
MyModule.send do |payload|
payload.my_text = "payload text"
end
end
MyModule's structure is like following.
module MyModule
class Payload
attr_accessor :my_text
def send
# do things with my_text
end
end
class << self
def send
payload = Payload.new
yield payload
payload.send
end
end
How can I test whether MyModule receives send method with a block, which assigns "payload text" to payload.my_text?
Currently I'm only testing expect(MyModule).to receive(:send).once. I looked through and tried Rspec yield matchers but cannot get things done. (Maybe I've ben searching for wrong keywords..)
The easiest way is to insert a double as the yield argument, which you can make an assertion on.
payload = Payload.new
allow(Payload).to receive(:new).and_return(payload)
test_target
expect(payload.my_text).to eq 'payload text'
Alternatively you could also use expect_any_instance_of, but I'd always prefer to use a specific double instead.
I would mock MyModule to yield another mock, that would allow speccing that my_text= is called on the yielded object.
let(:payload) { instance_double('Payload') }
before do
allow(MyModule).to receive(:send).and_yield(payload)
allow(payload).to receive(:my_text=).and_return(nil)
end
# expectations
expect(MyModule).to have_received(:send).once
expect(payload).to have_received(:my_text=).with('payload text').once

Ruby - Proc.call - catching exceptions

I have problem with catching exceptions and errors from calling Proc object. See my code:
def method1
.. do something ...
end
def method2
.. do something ...
end
def run_method_safely(proc_obj)
begin
proc_obj.call
rescue => e
puts "Error man!"
... do something ...
end
end
I have few methods (here I have just method1, method2 but in code i have much more methods) which are run by method run_method_safely. I'm doing that this way because I don't want to have every method in begin-rescue block so I wrote method what takes care about that. See:
.
run_method_safely(Proc.new { method1 })
run_method_safely(Proc.new { method2 })
.
The problem is when proc_obj.call is executed with error (method is missing, Webdriver - no such element found or whatever) rescue block is not executed, program is running like nothing happened.
Does anybody know how to catch errors and exceptions from Proc code?
Are you sure your sample is correct?
def safe(proc)
begin
proc.call
rescue
puts "error"
end
end
def m1
puts "m1"
raise
end
def m2
puts "m2"
end
safe(Proc.new { m1 })
safe(Proc.new { m2 })
Prints
m1
error
m2
for me.
Btw: you do not need to wrap the call in a Proc, you can pass the Method object directly.
safe(method(:m1))
I think it should be
rescue Exception => e
The default for rescue is not to catch all errors, but only those which are derived from StandardError.
Side note: I was not aware that the syntax
rescue => e
is valid. => is a binary operator, and you don't provide a left argument for this.

Easily create an Enumerator

When creating methods that yield, sometimes we want it to return an Enumerator if no block is given. The recommended way is basically return to_enum(:name_of_method, [args]) unless block_given?. However, it's a pain to have to type that for every method that does this. Ruby being ruby, I decided to create a make_enum method, similar to attr_accessor, which does this for me:
class Module # Put this in a mixin, but for the purposes of this experiment, it's in Module
def make_enum *args
args.each do |name|
old_method = instance_method(name)
define_method(name) do |*args, &block|
next to_enum(name, *args) unless block
old_method.bind(self).call(*args, &block)
end
end
end
end
Now I can use it like so:
class Test
def test
yield 1
yield 2
end
make_enum :test
end
t = Test.new
t.test { |n| puts n }
# 1
# 2
t.test.to_a #=> [1, 2]
And it works! But it doesn't work if make_enum is before the method definition.
How can I get this method to work before defining a method, so that the following works? Perhaps I need to make use of method_added?
class Test
make_enum :test
def test
yield 1
yield 2
end
end
I don't know if it's a bad idea for it to be before the method, but my reason for thinking that it would be nice to do that is that it better matches the way we use attr_accessor and the like.
Whereas attr_ methods create instance methods newly, your make_enum modifies an existing method, which is rather similar to protected, private, and public methods. Note that these visibility methods are used either in the form:
protected
def foo; ... end
or
protected def foo; ... end
or
def foo; ... end
protected :foo
The latter two ways are already available with your make_enum. Especially, the second form is already possible (which Stefan also notes in the comment). You can do:
make_enum def test; ... end
If you want to do the first form, you should try to implement that in your make_enum definition.

Using ruby blocks with method calls

The following code works perfectly.
#doc = open(link) { |f| Hpricot(f) }
But I want to use the following code, which doesn't seem to play well with the Hpricot block (e.g. #doc is a TempFile object, not a Hpricot document object)
#doc = resolve_link(link) { |f| Hpricot(f) }
def resolve_link(link)
begin
return open(link)
rescue
logger.debug("#{$!} for link #{link}")
raise Exceptions::ErrorResolvingLink.new("Cannot resolve link #{link}.")
end
end
Any idea how I can get the second version of the code to work?
You're calling resolve_link with a block but you're not passing that block down to open. Try this instead:
def resolve_link(link, &block)
begin
return open(link, &block)
#...
You have to use yield to invoke the block.
See this answer for a very simple example:
Blocks and yields in Ruby
So something along the lines
def resolve_link(link)
...
yield ( some_value_to_pass_to_the_block )
...
end
Should work.

Resources