[rspec-users] response.body.should be_xml_with -- how to do nested xpath matches

Zach Dennis zach.dennis at gmail.com
Sun Mar 8 14:23:05 EDT 2009


On Sun, Mar 8, 2009 at 2:03 PM, David Chelimsky <dchelimsky at gmail.com> wrote:
> On Sun, Mar 8, 2009 at 12:00 PM, Phlip <phlip2005 at gmail.com> wrote:
>> RSpecsters:
>>
>> I like nested XPath expressions, because the outer XPath assertion can clip
>> the diagnostic of the inner XPath assertion. You don't get the whole page
>> spewed into your face when you fault!
>>
>> This specification, for example, tests Yury Kotlyarov's user login page,
>> from:
>>
>>  http://github.com/yura/howto-rspec-custom-matchers/tree/master
>>
>>  it 'should have xpathic tags' do
>>    render '/users/new'
>>
>>    response.body.should be_xml_with{
>>      xpath :form, :action => '/users' do
>>        xpath :fieldset do
>>          xpath :'legend[ contains(., "Personal Information") ]' and
>>          xpath :'label[ contains(., "First name") ]' and
>>          xpath :input, :type => 'text', :name => 'user[first_name]'
>>        end
>>      end
>>    }
>>  end
>
> This is nice, but the abstractions are operating at different levels.
> The keyword is "xpath" but the args passed to that look nothing like
> xpath. How about something like:
>
> response.body.should be_xml_with do
>  form :action => '/users' do
>    fieldset do
>      legend "Personal Information"
>      label "First name"
>      input :type => 'text', :name => 'user[first_name]'
>    end
>  end
> end
>

I like this a lot.

>
>
>
>>
>> That tests this (otherwise innocuous) new.html.erb:
>>
>> <form action="/users">
>>  <fieldset>
>>    <legend>Personal Information</legend>
>>    <ol>
>>      <li id="control_user_first_name">
>>        <label for="user_first_name">First name</label>
>>        <input type="text" name="user[first_name]" id="user_first_name" />
>>      </li>
>>    </ol>
>>  </fieldset>
>> </form>
>>
>> If that code had any major complex <%= erb %> activity, the tests^W
>> specifications would keep it honest.
>>
>> Let's change the specification, to simulate a bug, and try it:
>>
>>    xpath :input, :type => 'text', :name => 'user[first_nome]' # was _name
>>
>> That provides this mind-blast of errata:
>>
>> '/users/new should have xpathic tags' FAILED
>> xpath: "descendant-or-self::input[@type = $type and @name = $name]"
>> arguments: {"name"=>"user[first_nome]", "type"=>"text"}
>>
>> xml context:
>>
>> <fieldset>
>>  <legend>
>>    Personal Information
>>  </legend>
>>  <ol>
>>    <li id='control_user_first_name'>
>>      <label for='user_first_name'>
>>        First name
>>      </label>
>>      <input name='user[first_name]' type='text' id='user_first_name'/>
>>    </li>
>>  </ol>
>> </fieldset>
>>
>> assert{ ( ( xpath(:"legend[ contains(., \"Personal Information\") ]") ) and
>>     ( ( ( xpath(:"label[ contains(., \"First name\") ]") ) and
>>    ( xpath(:input, { :type => "text", :name => "user[first_nome]" }) ) ) ) )
>> }
>>  --> nil - should pass
>>
>> xpath(:"legend[ contains(., \"Personal Information\") ]")
>>  --> <legend> ... </>
>>
>> xpath(:"label[ contains(., \"First name\") ]")
>>  --> <label for='user_first_name'> ... </>
>>
>> xpath(:input, { :type => "text", :name => "user[first_nome]" })
>>  --> nil
>>
>> ./spec/views/users/new.html.erb_spec.rb:63:
>> script/spec:5:
>>
>> Finished in 0.116823 seconds
>>
>> 2 examples, 1 failure
>>
>> Note that the error message restricted itself to the XHTML inside the <form>
>> tag. This is a major benefit when diagnosing a huge page that failed. (But
>> also note that your HTML, like your code, should come in small reusable
>> snippets, such as partials, and that these should get tested directly!)
>>
>> Soon I will upgrade this system to use Nokogiri instead of (>cough<) REXML.
>>
>> Now, while I go and put the "simple" matcher that does this onto Twitter,
>> YouTube, Mingle, Facebook, Mindfuck, Reddit, Tumblog, LinkedIn, and Gist,
>> you all can just read it below my sig.
>>
>> --
>>  Phlip
>>  http://www.zeroplayer.com/
>>
>> require File.dirname(__FILE__) + "/../../spec_helper"
>> require 'assert2/xpath'
>> require 'spec/matchers/wrap_expectation'
>>
>> Spec::Runner.configure do |c|
>>  c.include Test::Unit::Assertions
>> end  #  TODO blog this
>>
>> describe "/users/new" do
>>
>>  it "should have user form" do
>>    render '/users/new'
>>    response.should have_form('/users') do
>>      with_field_set 'Personal Information' do
>>        with_text_field 'First name', 'user', 'first_name'
>>      end
>>    end
>>  end
>>
>>  class BeXmlWith
>>
>>    def initialize(scope, &block)
>>      @scope, @block = scope, block
>>    end
>>
>>    def matches?(stwing, &block)
>>      waz_xdoc = @xdoc
>>
>>      @scope.wrap_expectation self do
>>        @scope.assert_xhtml stwing
>>        return (block || @block || proc{}).call
>>      end
>>    ensure
>>      @xdoc = waz_xdoc
>>    end
>>
>>    attr_accessor :failure_message
>>
>>    def negative_failure_message
>>      "yack yack yack"
>>    end
>>  end
>>
>>  def be_xml_with(&block)
>>    BeXmlWith.new(self, &block)
>>  end
>>
>>  def be_xml_with_(&block)
>>    waz_xdoc = @xdoc
>>    simple_matcher 'yo' do |given, matcher|
>>      wrap_expectation matcher do
>>        assert_xhtml given  #  this works
>>        block.call  #  crashes with a nil.first error!
>>      end
>>    end
>>  ensure
>>    @xdoc = waz_xdoc
>>  end
>>
>>  it 'should have xpathic tags' do
>>    render '/users/new'
>>
>>    response.body.should be_xml_with{
>>      xpath :form, :action => '/users' do
>>        xpath :fieldset do
>>          xpath :'legend[ contains(., "Personal Information") ]' and
>>          xpath :'label[ contains(., "First name") ]' and
>>          xpath :input, :type => 'text', :name => 'user[first_name]'
>>        end
>>      end
>>    }
>>  end
>>
>> end
>>
>> _______________________________________________
>> 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
>



-- 
Zach Dennis
http://www.continuousthinking.com
http://www.mutuallyhuman.com


More information about the rspec-users mailing list