RSpec "should_yield"?

Written on 4:51:00 PM by S. Potter

While writing some specifications for the 0.2.0 version of Twitter4R (soon to be released - honestly), I realized there was no clean way to do #should_yield, so I thought I would offer my suggestion for the developers of RSpec to critique as appropriate. Assume we have a method signature that takes a block and should yield an object to the block given (if one is given). Something like the following:

class Client
  class << self
    def configure(&block)
      # implementation of Client.configure
    end
    # rest of class methods for Client class go here...
  end
  # rest of instance methods for Client class go here....
end
Where usage for the Client.configure method should look something like the following:
Client.configure do |conf|
  conf.proxy_host = 'myproxyhost'
  conf.proxy_port = 8080
  conf.proxy_user = 'me'
  conf.proxy_pass = 'mypass'
end
The object yielded to the block given is an instance of Config which has settable attributes: proxy_host, proxy_port, proxy_user, proxy_pass. The question is how do we write RSpec specifications for this? First we need to define what we want to specify for the expected behavior. My first attempt is to describe (in English) that:
  • Client.configure should
    • accept a block as argument
    • yield a Config instance to the given block
The way I did this in the Twitter4R (open source Ruby library for the Twitter REST API) project specifications was something like the following:
describe Client, ".configure" do
  before(:each) do
    @has_yielded = false
    @block = Proc.new do |conf|
      conf.is_a?(Config).should be_true
      @has_yielded = true
    end
  end

  it "should accept a block as argument" do
    lambda {
      Client.configure {}
    }.should_not raise_error
  end

  it "should yield a Config object to given block" do
    Client.configure(&block)
    @has_yielded.should be_true
  end

  after(:each) do
    @has_yielded, @block = nil
  end
end
What I would really like to do something like the following instead:
describe Client, ".configure" do
  it "should accept a block as argument" do
    lambda {
      Client.configure {}
    }.should_not raise_error
  end

  it "should yield a Config object to given block" do
    lambda {
      Client.configure(&spec_block(1))
    }.should yield_with(Config)
  end
end
I haven't extended the Proc class to this point, but if/when I do I will post it to this blog and perhaps also to the RSpec mailing list. If you have suggestions on how to improve the more manual way (the first RSpec code listing above), please do let me know. I have not yet played around with inheritable describe blocks yet, but I have hunch that would DRY up some of the code for if needed for many contexts. Alternatively if you have suggestions on how to improve the second RSpec code listing (directly above) to be more clear, please do let me know that too. I realize the developers of RSpec don't want to have too many #should_XXX methods, so I am open to suggestions (since I also agree with that notion). UPDATE: I created a new feature request and dialog with RSpec developers at: [#11949]

If you enjoyed this post Subscribe to our feed

No Comment

Post a Comment