[rspec-users] Evaluating shared example customisation block before shared block

David Chelimsky dchelimsky at gmail.com
Sun Aug 1 10:43:42 EDT 2010


On Jul 31, 2010, at 1:06 PM, Myron Marston wrote:

>> You can still get the same outcome, but you have to implement it in the group like this:
> 
>> unless defined?(:foo)
>> def foo; "foo"; end
>> end
> 
> Good point--I hadn't thought of that.  The one issue I see with it is
> that the author of the shared example group may not have knowledge of
> which helper methods consumers will need to override.

That's no different from methods that have default values for arguments:

  def foo(bar, baz = :default)

If you provide only 1 arg, all is well, but the first one is required. Here's the same idea expressed in a group:

shared_examples_for "foo" do
  unless defined?(:bar)
    raise "you need to supply a bar() method"
  end

  unless defined?(:baz)
    def baz; :default; end
  end
end

> So he/she
> either defines all helper methods that way, or guesses about which
> ones to define that way (and potentially guesses wrong).
> 
>> Maybe a DSL method while I'm working on it?  Maybe:
>> 
>>   default_helper(:foo) do
>>     "foo"
>>   end
>> 
>> WDYT?
> 
> If we go the route of having the customization block evaluated first,
> then I like the idea, but I'm generally wary of adding more DSL
> methods to RSpec.  I think we should be careful to only add new DSL
> methods that many people will find useful.  If you find it useful,
> it's very easy to use it in your project without it being part of
> RSpec: just define default_helper in a module, and use config.extend
> YourModule in the RSpec.configuration block.  (Note that I'm _not_
> against adding this to RSpec: I just want to be sure we don't add a
> bunch of DSL methods that have limited usefulness.)
> 
> Looking back at the initial example that prompted the thread, it looks
> to me like the primary use case for evaluating the customization block
> first is so that you can parameterize the shared example group's
> example descriptions.  (There may be other use cases for defining a
> class-level helper methods, but none springs to mind).  I also do this
> frequently.  Often times I have something like this:
> 
> [:foo, :bar, :baz].each do |method|
>  it "does something for #{method}" do
>     subject.send(method).should ...
>  end
> end
> 
> In this case I'm using the method parameter at the class level (to
> interpolate into the description string) and at the instance level
> (within the example itself).
> 
> If we evaluated the customization block first, it would allow this,
> but you'd have to define both an instance and class helper:
> 
> it_should_behave_like "something" do
>  def self.method_name; :foo; end
>  def method_name; :foo; end
> end
> 
> I think this is a clunky way to essentially pass a parameter to the
> shared example group.  Better would be something like this:
> 
> it_should_behave_like "something" do
>  providing :method_name, :foo
> end
> 
> The instance of the shared example group provides :foo as the value of
> the method_name parameter.  providing simply defines a class and an
> instance helper method with the given value.
> 
> I've written up an untested gist with a start for the code that would
> implement this:
> 
> http://gist.github.com/502409
> 
> I think there's value in evaluating the customization block first and
> value in evaluating it last.  We can get the best of both worlds if we
> limit what's evaluated first to a subset (say, a few DSL methods, and
> maybe all class method definitions), extract it, and evaluate that
> first, then evaluate the shared block first, then evaluate the
> customization block.  The gist demonstrates this as well.  This may
> confuse people, but it does give us the best of both worlds, I think.

Agreed on both points: best of both worlds and confusing :)

When I said "maybe a DSL" I was thinking only in the context of the shared group, not in the consuming group.

What makes the example in your gist confusing to me is that we start to get into a different mental model of what a shared group is. Based on recent changes, for me, it's just a nested example group, which has well understood scoping rules. This introduces a new set of scoping rules that not only make this use case confusing, but it will lead to an expectation that this DSL be made available in other constructs.

The particular issue of simple values being used in the docstrings and the examples themselves (i.e. exposed to everything in the block scope) could be handled like this:

shared_examples_for "blah" do |a,b|
  ...
end

it_should_behave_like "blah", 1, 2

That wouldn't have worked with the old implementation, but it would work perfectly well now. This would also "just work" with hash-as-keyword-args:

shared_examples_for "blah" do |options|
  it "blah #{options[:a]}" do
    ..
  end
end

it_should_behave_like "blah", :a => 1

Now you can do this:

[1,2,3].each do |n|
  it_should_behave_like "blah", :a => n
end

And we can still use the customization block to define methods, hooks (before/after) and let(). Now it just feels like the rest of RSpec.

Thoughts?



> 
> Myron
> 
> 
> On Jul 31, 12:56 am, Ashley Moran <ashley.mo... at patchspace.co.uk>
> wrote:
>> On 31 Jul 2010, at 1:10 AM, David Chelimsky wrote:
>> 
>>> You can still get the same outcome, but you have to implement it in the group like this:
>> 
>>> unless defined?(:foo)
>>>  def foo; "foo"; end
>>> end
>> 
>> Maybe a DSL method while I'm working on it?  Maybe:
>> 
>>   default_helper(:foo) do
>>     "foo"
>>   end
>> 
>> WDYT?
>> 
>>> I think it's a good trade-off to put that burden on the group (and it's author) rather that the consumers of the group.
>> 
>> Agreed, it'd cause a lot of duplication of effort the other way round.
>> 
>> --http://www.patchspace.co.uk/http://www.linkedin.com/in/ashleymoran
>> 
>> _______________________________________________
>> rspec-users mailing list
>> rspec-us... at rubyforge.orghttp://rubyforge.org/mailman/listinfo/rspec-users
> _______________________________________________
> rspec-users mailing list
> rspec-users at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users



More information about the rspec-users mailing list