Edmund Lee
Edmund Lee

Reputation: 2572

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.

Upvotes: 1

Views: 203

Answers (1)

Sergio Tulentsev
Sergio Tulentsev

Reputation: 230286

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

Upvotes: 3

Related Questions