[rspec-users] Scaling shared behavior (was: Re: Message expectation that verifies result of passed block)

Paul Hinze paul.t.hinze at gmail.com
Tue Feb 2 09:58:34 EST 2010

A bit of a delayed reply, but I appreciate all the feedback everyone.
What a helpful list this is! :)

Nicolás Sanguinetti <godfoca at gmail.com> on 2010-01-28 at 15:45:
> You're definitely testing too much implementation and not enough behavior.

This was the overwhelming opinion of the group, and I see what you are
all saying. 

> Basically, what you want to spec, is that provided some options, when
> you call a certain method of your form builder, you get a certain html
> output. At least that's how I would approach the problem.
> it "produces a correctly formatted FOO input" do
>   html = @builder.foo_text(...)
>   html.should have_tag("label", :for => "foo")
>   html.should have_tag("input", :id => "foo")
> end

This is more or less exactly the structure that current specs have.  And
the (mistakenly-designed, I now realize) refactor I was hoping to make
was to address the problem of _re-verifying_ all of the behavior of
`question` in every formbuilder method.

Here is how we're currently doing it:

describe 'question', :shared_behavior => true do
  it 'marks the question required' do
    # stub question required in model
    xhtml = @builder.send(@current_method) # [A]
    xhtml.should have_tag('li.required')
  # ... more examples describing question behavior [B]

describe '#question' do
  before { @current_method = 'question' }
  it_should_behave_like 'question'

describe '#text' do
  before { @current_method = 'text' }
  it_should_behave_like 'question'
  # ... examples describing text behavior

This worked great at first, until we got to the point of there being
more than 50 examples at [B] for `question`, and there being more than
30 other methods.  I also realized that some methods that share question
behavior require certain options to properly pass through the question
shared example group.  So I had to add another argument to [A] and
maintain this knowledge across the suite.

Not only that, but I realized that question is not the only shared
behavior.  Add that to the mix and you start to get structures like

describe 'collection', :shared => true do
  # ... examples describing collection behavior

describe '#collection' do
  before do
    @current_method = :collection
    @default_options = { :collection => [:foo, :bar, :baz] }
  it_should_behave_like 'question'
  it_should_behave_like 'collection'

describe '#dependent_collection' do
  before do
    @current_method = :dependent_collection
    @default_options = { :collection => [:foo, :bar, :baz], :depends_on => :qux }
  it_should_behave_like 'question'
  it_should_behave_like 'collection'
  # ... examples describing dependent_collection behavior

This gets unruly and difficult to read and maintain very quickly, and
because our group has several devs who touch this code, the pattern is
not always followed correctly.

So now I sit with these disadvantages:

 1) a relatively slow test suite for my formbuilder, since every detail
    of any shared behavior is verified over and over
 2) some mysterious instance variables i need to maintain across all
    example groups in order to keep shared groups testing the right
 3) strange and unintuitive coupling across the test suite

The advantage we're getting, of course, is that behavior is properly
speced with this structure (when we pull it off properly at least).

I started this thread with the thought that I could simplify this by
basically flipping out 'it_should_behave_like' with 'it_should_call',
but you folks identified that as slipping down into verifying
implementation rather than behavior.

So the question that I pose to you, list, is this: is there any way to
change the way we're dealing with this problem to minimize the
disadvantages I mentioned above or is the answer "yup, that's pretty
much how you would need to set this up"?

Thanks again for all your feedback and attention folks, it has been
incredibly helpful.



More information about the rspec-users mailing list