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

Nicolás Sanguinetti godfoca at gmail.com
Thu Jan 28 16:43:35 EST 2010


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.

-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
>


More information about the rspec-users mailing list