[rspec-users] Message expectation that verifies result of passed block

Matt Wynne matt at mattwynne.net
Thu Jan 28 17:45:50 EST 2010


On 28 Jan 2010, at 21:43, Nicolás Sanguinetti wrote:

> You're definitely testing too much implementation and not enough  
> behavior.
>
> 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.
>
> So I would have something like this:
>
> it "produces a correctly formatted FOO input" do
>  html = @builder.foo_text(...)
>  html.should == "<label for='foo'>FOO</label> <input id='foo'....>"
> end
>
> Since testing generated HTML like that sucks, I would proceed to use
> something like http://github.com/fnando/rspec-hpricot-matchers, and
> say
>
> 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
>
> Or something like that. But then if you stop using that
> question(*args, &block) method, and refactor to a different
> implementation, specs should continue to pass.

What he said.


> -foca
>
> On Thu, Jan 28, 2010 at 7:14 PM, Paul Hinze <paul.t.hinze at gmail.com>  
> wrote:
>> Ashley Moran <ashley.moran at patchspace.co.uk> on 2010-01-28 at 13:28:
>>>
>>> On Jan 28, 2010, at 1:29 pm, Paul Hinze wrote:
>>>
>>>> I believe the lack of ability to use this notation comes down to  
>>>> a ruby
>>>> limitation, but I'm not sure.  If that's the case, then we would  
>>>> need a
>>>> specific argument expectation (along the lines of my suggestion)  
>>>> that
>>>> executes in a context in which it can call the block.
>>>
>>> I can't find a solution, I suspect Ruby 1.8 can't do this, but I'm  
>>> guessing.
>>>
>>> Can I ask why you want to do this though?  As another example, it
>>> would be unusual to spec something like:
>>>
>>>   @array = [1, 2, 3]
>>>   @array.should_receive(:map).with(block_that_doubles_values)
>>>
>>> You'd instead check that the array that came out was [2, 4, 6].
>>
>> I'm trying to spec a large set of what essentially come down to
>> decorator methods in a Rails FormBuilder extension plugin.  What this
>> boils down to is methods that wrap rails FormBuilder methods, so
>> `f.text(*args)` ends up calling `f.text_field(*args)` to generate an
>> <input> tag, but only after it does its own logic and wrapping, which
>> among a bunch of other things wraps the output in an <li>.
>>
>> So the methods run the gamut in complexity from 'f.radio' to
>> 'f.dependent_collection' to 'f.sigma', but much of the common code is
>> wrapped up in a method called 'f.question', which does the outer <li>
>> wrapping, required field detection, label and error display, and a  
>> few
>> other common things required by every control we use in our forms.
>>
>> So most of our methods have this basic structure:
>>
>>  class OurFormBuilder < ActionView::Helpers::FormBuilder
>>    def foo_text(method, options={})
>>      foo_option = options.delete(:foo_option)
>>      options[:value] ||= 'FOO'
>>
>>      # some logic, using foo_option somewhere...
>>
>>      question(method, options) do |remaining_options|
>>        'FOO -->' + text_field(method, remaining_options) + '<-- FOO'
>>      end
>>    end
>>  end
>>
>> I started out with a nice spec for `question`'s behavior and made  
>> it all
>> in a shared group, but because of the number of examples just for
>> question and the number of methods that call it (so both  
>> performance and
>> complexity), I'm thinking about switching to message expectations  
>> in all
>> of my `foo_text`-style method specs:
>>
>>  describe 'foo_text'
>>    it 'calls text field with the proper options' do
>>       
>> @builder.should_receive(:text_field).with(:some_method, :proper_args)
>>      @builder.foo_text(:some_method)
>>    end
>>    it 'yields a wrapped text_field into question' do
>>      # dont test rails text_field
>>      text_field_return = "BOOGA"
>>      @builder.stub!(:text_field).and_return(text_field_return)
>>
>>      expected = "FOO -->" + text_field_return + "<-- FOO"
>>       
>> @builder 
>> .should_receive 
>> (:question).with(:some_method).with_a_block_yielding(expected)
>>
>>      @builder.foo_text(:some_method, :some => options)
>>    end
>>    it 'properly returns the result of the call to question' do
>>      @builder.stub!(:question).and_return('BOOGA')
>>      xhtml = @builder.foo_text(:some_method)
>>      xhtml.should == 'BOOGA'
>>    end
>>  end
>>
>> I'd appreciate any feedback that folks might be willing to give.
>> Particularly I realize the following:
>>
>>  (a) This might be testing implementation too much (possible)
>>  (b) The architecture of the whole plugin needs a serious refactor to
>>     increase modularity and decouple the components (very likely); a
>>     dash of decent OO design could really help this whole situation,
>>     and it's something I'm planning on tackling down the road
>>
>> For now, I'm just trying to push things in the right direction, and I
>> _think_ the .with_a_block_yielding(value)  
>> or .with(block_yielding(value))
>> argument verification would help me do that.
>>
>> Thanks for your time!
>>
>> Paul
>> _______________________________________________
>> rspec-users mailing list
>> rspec-users at rubyforge.org
>> http://rubyforge.org/mailman/listinfo/rspec-users
>>
> _______________________________________________
> rspec-users mailing list
> rspec-users at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users

cheers,
Matt

http://mattwynne.net
+447974 430184



More information about the rspec-users mailing list