[rspec-devel] [ rspec-Feature Requests-13487 ] Parameterize Shared Behaviours

noreply at rubyforge.org noreply at rubyforge.org
Mon Nov 19 00:21:15 EST 2007


Feature Requests item #13487, was opened at 2007-08-29 09:42
You can respond by visiting: 
http://rubyforge.org/tracker/?func=detail&atid=3152&aid=13487&group_id=797

Category: None
Group: None
Status: Open
Priority: 3
Submitted By: David Chelimsky (dchelimsky)
Assigned to: Nobody (None)
Summary: Parameterize Shared Behaviours

Initial Comment:
There has been discussion of parameterized shared behaviours, but t

The current implementation of shared behaviours copies examples into the execution scope of the including behaviour. This gives the examples access to state defined in before(:each) in the including example. This works well, but does some a bit like black magic:

describe "a RESTful controller", :shared => true do
  it "should find all instances of the resource" do
    @model_class.should_receive(:find).with(:all)
    get 'index'
  end
end

describe FooController do
  before(:each) do
    @model_class = Foo
  end
  it_should_behave_like "a RESTful controller"
end

The steps in the new Story Runner (Given, When, Then) all take arguments, supporting reusable blocks. We should do the same thing with shared behaviours. 

I think that parameterizing these would be a cleaner solution, but I think it would require a separate construct (internally) than the one we have - if parameterized it should not share state with the including behaviour. But I can imagine something like this:

#DOES NOT EXIST YET
describe "a RESTful controller" do |model_class|
  it "should find all instances of the resource" do
    model_class.should_receive(:find).with(:all)
    get 'index'
  end
end

describe FooController do
  #DOES NOT EXIST YET
  it_should_behave_like "a RESTful controller", Foo
end

We might not even need the :shared => true flag if the describe block has block args - arity > 0 could be the indicator to share.

Now this approach has it's drawbacks as well. If you end up w/ a long list of block args, you'd best get them in the right order. Sharing instance variables is its own brand of voodoo, but at least you can name them well. I guess we could add a hash:

  it_should_behave_like "a RESTful controller", :model_class => Foo

But that starts to get ugly pretty quickly as well.

Thoughts?

----------------------------------------------------------------------

Comment By: Chad Humphries (spicycode)
Date: 2007-11-19 00:21

Message:
Moved to http://rspec.lighthouseapp.com/projects/5645-rspec/tickets/110-13487-parameterize-shared-behaviours#ticket-110-2

----------------------------------------------------------------------

Comment By: Assaf Arkin (assaf)
Date: 2007-09-12 20:06

Message:
I would prefer to get rid of :shared=>true by adding a new method, say shared instead of describe.  It's easier to scan through the code and identity the shared behaviors.  Arity would make sense when I'm writing a shared behavior, but much harder to spot them when I'm reading the code.

And another +1 to parameterizing shared behaviors.  Right now I handle that by defining a method for use by the  shared behavior assumes exist, so:

  describe "using shared" do
    it_should_behave_like "a RESTful controller"
    define_method(:model_class) { Foo }
  end

This can be done with hashes (create methods to hold those values), or named arguments, but unfortunately not both.

----------------------------------------------------------------------

Comment By: Tom Stuart (tstuart)
Date: 2007-08-30 03:33

Message:
David: in the vaguely-analogous case of Rails partials, controller state is shared (i.e. access to the instance variables still works) even when the partial is parameterised (i.e. render :partial => ..., :locals => { ... }), so the combination of both in shared behaviours may feel familiar to Rails users at least.

----------------------------------------------------------------------

Comment By: Dan North (tastapod)
Date: 2007-08-29 19:27

Message:
I'm  missing something here. Can't all this shared behaviour just be achieved using mixins?

module BehavesLikeARestfulController
  # expects @model_class to be defined

  it "should find all instances of the resource" do
    @model_class.should_receive(:find).with(:all)
    get 'index'
  end
end

describe FooController do
  @model_class = Foo
  include BehavesLikeARestfulController
end

The example runner will re-initialise @model_class for each example (it-block) in the mixin because each one runs in a separate object instance.

Also, you can put before(:each) code in the module to make it self-contained, and the runner should Do The Right Thing (i.e. add it to any additional before(:each) in the specific describe block).

How does this help me? I wasn't around for the original conversations that led to :shared behaviours in the first place, so apologies if I'm going over old ground.

----------------------------------------------------------------------

Comment By: David Chelimsky (dchelimsky)
Date: 2007-08-29 16:18

Message:
I was thinking it should either be shared state or parameterized, but not both. That strikes me as the least confusing option. But I'm convincable.

----------------------------------------------------------------------

Comment By: Ian Dees (undees)
Date: 2007-08-29 15:22

Message:
+1 on letting the describe block's arity indicate shared-itude.

>From there, the test author can choose whether to put a few block parameters in a row or use a single hash parameter.

If this gets implemented, are you going to revoke shared behaviors' access to the state of the describe block where they're used?  I could see this causing confusion.  After all, shared Given/When/Then blocks definitely have access to their Scenario's state.

----------------------------------------------------------------------

Comment By: Tom Stuart (tstuart)
Date: 2007-08-29 10:44

Message:
This functionality would presumably be used in the presence of inheritance (or mixins etc) in the code being specified, so it'd be nice to support something that behaves like spec inheritance, e.g. the ability to extend or override individual shared behaviors, but it's hard to see how that could work. It's certainly easier to demand that shared behaviors be kept fine-grained enough to be mixed-and-matched as required.

----------------------------------------------------------------------

Comment By: Tom Stuart (tstuart)
Date: 2007-08-29 10:35

Message:
Using a hash looks ugly but arguably is more natural and provides maximum flexibility, particularly in a situation where one has a healthy set of default options and wants to tweak individual values as necessary:

 describe FooController do
   it_should_behave_like "a RESTful controller", options.merge { :model_class => Foo }
 end

Of course, if you do it with block args then this is already available to the user as a special case!

The idea of getting rid of :shared => true in the presence of describe block args is really cool.

----------------------------------------------------------------------

You can respond by visiting: 
http://rubyforge.org/tracker/?func=detail&atid=3152&aid=13487&group_id=797


More information about the rspec-devel mailing list