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

David Chelimsky dchelimsky at gmail.com
Sun Mar 8 14:03:15 EDT 2009


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




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


More information about the rspec-users mailing list