Edmund Lee Edmund Lee - 1 month ago 9
Ruby Question

Test if a method takes a block

How can I verify if

Foobar#some_method
takes a block. Something simliar to
Foobar.new.respond_to?(:some_method)


class Foobar
def some_method
yield
end
end


Why?

This is useful for testing contract interface. Ensuring the method I stub has an unchanged API.

Ways I've tried

mth = Foobar.new.method(:some_method)
mth.parameters


this returns a list of parameters (what Rspec uses essentially). It works if I have an argument as block like this:

def some_method(&blk)
end


But if a method uses
yield
instead, I got nothing from
#parameters
.

This is for adding specs for ensuring the interface with the ugly outside world. So I know how a method is used. But if there is an API change, I'd like specs to fail.

Answer Source

How can I verify if Foobar#some_method takes a block.

Every method in ruby takes (can take) a block. It may simply choose not to yield. So that's what you need to check, I think: if the method yielded or not.

RSpec has a number of yield expectations: https://relishapp.com/rspec/rspec-expectations/docs/built-in-matchers/yield-matchers

RSpec.describe "yield_control matcher" do
  specify { expect { |b| MyClass.yield_once_with(1, &b) }.to yield_control }
  specify { expect { |b| MyClass.dont_yield(&b) }.not_to yield_control }
  specify { expect { |b| MyClass.yield_twice_with(1, &b) }.to yield_control.twice }
  specify { expect { |b| MyClass.yield_twice_with(1, &b) }.to yield_control.exactly(2).times }
  specify { expect { |b| MyClass.yield_twice_with(1, &b) }.to yield_control.at_least(1) }
  specify { expect { |b| MyClass.yield_twice_with(1, &b) }.to yield_control.at_most(3).times }

end

So for your case it should be something like this:

expect{ foobar.some_method }.to yield_control