<?xml version='1.0' encoding='utf-8' ?>
<rss version='2.0' xmlns:dc='http://purl.org/dc/elements/1.1/'>
  <channel>
    <title>Wolfmans Howlings: XPath matchers for rspec</title>
    <link>http://blog.wolfman.com/articles/2008/1/2/xpath-matchers-for-rspec</link>
    <description>A programmers Blog about Ruby, Rails and a few other issue</description>
    <language>en-us</language>
    <ttl>40</ttl>
    <item>
      <title>XPath matchers for rspec</title>
      <description>
        &lt;p&gt;I've been working on a project that is mostly Java for the last many
        months, so haven't had much Ruby or Rails stuff to share.&lt;/p&gt;
        
        &lt;p&gt;However one thing I found when working on my tests in Java was an
        xpath matcher for JUnit 4.0 using the
        &lt;a href=&quot;http://code.google.com/p/hamcrest&quot;&gt;Hamcrest&lt;/a&gt; libraries.&lt;/p&gt;
        
        &lt;p&gt;When I dropped back into Ruby to write some cron scripts that process
        information from the database and generate xml files I wanted to check
        the script with an rspec, and check the xml files it was generating.&lt;/p&gt;
        
        &lt;p&gt;To do this I wanted to use a similar matcher to the Hamcrest ones, but
        use it in RSpec.&lt;/p&gt;
        
        &lt;p&gt;I Googled around and found a simple
        &lt;a href=&quot;http://gensym.org/2007/7/15/custom-xpath-matcher-for-rspec&quot;&gt;example&lt;/a&gt;,
        but it wasn't very sophisticated and didn't check what I needed so I
        upgraded it to do the kind of matches I needed, the results are here.&lt;/p&gt;
        
        &lt;pre&gt;
        &lt;span class=&quot;ident&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;rexml/document&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;'&lt;/span&gt;
        &lt;span class=&quot;ident&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;rexml/element&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;'&lt;/span&gt;
        
        &lt;span class=&quot;keyword&quot;&gt;module &lt;/span&gt;&lt;span class=&quot;module&quot;&gt;Spec&lt;/span&gt;
          &lt;span class=&quot;keyword&quot;&gt;module &lt;/span&gt;&lt;span class=&quot;module&quot;&gt;Matchers&lt;/span&gt;
        
            &lt;span class=&quot;comment&quot;&gt;# check if the xpath exists one or more times&lt;/span&gt;
            &lt;span class=&quot;keyword&quot;&gt;class &lt;/span&gt;&lt;span class=&quot;class&quot;&gt;HaveXpath&lt;/span&gt;
              &lt;span class=&quot;keyword&quot;&gt;def &lt;/span&gt;&lt;span class=&quot;method&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;xpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;attribute&quot;&gt;@xpath&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;xpath&lt;/span&gt;
              &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
        
              &lt;span class=&quot;keyword&quot;&gt;def &lt;/span&gt;&lt;span class=&quot;method&quot;&gt;matches?&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;attribute&quot;&gt;@response&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;response&lt;/span&gt;
                &lt;span class=&quot;ident&quot;&gt;doc&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;is_a?&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;REXML&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;Document&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;REXML&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;Document&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;attribute&quot;&gt;@response&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;ident&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;REXML&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;XPath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;doc&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;attribute&quot;&gt;@xpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;keyword&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;empty?&lt;/span&gt;
              &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
        
              &lt;span class=&quot;keyword&quot;&gt;def &lt;/span&gt;&lt;span class=&quot;method&quot;&gt;failure_message&lt;/span&gt;
                &lt;span class=&quot;punct&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;Did not find expected xpath &lt;span class=&quot;expr&quot;&gt;#{@xpath}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;&amp;quot;&lt;/span&gt;
              &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
        
              &lt;span class=&quot;keyword&quot;&gt;def &lt;/span&gt;&lt;span class=&quot;method&quot;&gt;negative_failure_message&lt;/span&gt;
                &lt;span class=&quot;punct&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;Did find unexpected xpath &lt;span class=&quot;expr&quot;&gt;#{@xpath}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;&amp;quot;&lt;/span&gt;
              &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
        
              &lt;span class=&quot;keyword&quot;&gt;def &lt;/span&gt;&lt;span class=&quot;method&quot;&gt;description&lt;/span&gt;
                &lt;span class=&quot;punct&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;match the xpath expression &lt;span class=&quot;expr&quot;&gt;#{@xpath}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;&amp;quot;&lt;/span&gt;
              &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
            &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
        
            &lt;span class=&quot;keyword&quot;&gt;def &lt;/span&gt;&lt;span class=&quot;method&quot;&gt;have_xpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;xpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt;
              &lt;span class=&quot;constant&quot;&gt;HaveXpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;xpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
        
            &lt;span class=&quot;comment&quot;&gt;# check if the xpath has the specified value&lt;/span&gt;
            &lt;span class=&quot;comment&quot;&gt;# value is a string and there must be a single result to match its&lt;/span&gt;
            &lt;span class=&quot;comment&quot;&gt;# equality against&lt;/span&gt;
            &lt;span class=&quot;keyword&quot;&gt;class &lt;/span&gt;&lt;span class=&quot;class&quot;&gt;MatchXpath&lt;/span&gt;
              &lt;span class=&quot;keyword&quot;&gt;def &lt;/span&gt;&lt;span class=&quot;method&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;xpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;attribute&quot;&gt;@xpath&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;xpath&lt;/span&gt;
                &lt;span class=&quot;attribute&quot;&gt;@val&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;val&lt;/span&gt;
              &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
        
              &lt;span class=&quot;keyword&quot;&gt;def &lt;/span&gt;&lt;span class=&quot;method&quot;&gt;matches?&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;attribute&quot;&gt;@response&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;response&lt;/span&gt;
                &lt;span class=&quot;ident&quot;&gt;doc&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;is_a?&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;REXML&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;Document&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;REXML&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;Document&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;attribute&quot;&gt;@response&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;ident&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;true&lt;/span&gt;
                &lt;span class=&quot;constant&quot;&gt;REXML&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;XPath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;each&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;doc&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;attribute&quot;&gt;@xpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;|&lt;/span&gt;
                  &lt;span class=&quot;attribute&quot;&gt;@actual_val&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;e&lt;/span&gt;
                  &lt;span class=&quot;keyword&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;REXML&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;Attribute&lt;/span&gt;
                    &lt;span class=&quot;ident&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;to_s&lt;/span&gt;
                  &lt;span class=&quot;keyword&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;REXML&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;Element&lt;/span&gt;
                    &lt;span class=&quot;ident&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;text&lt;/span&gt;
                  &lt;span class=&quot;keyword&quot;&gt;else&lt;/span&gt;
                    &lt;span class=&quot;ident&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;to_s&lt;/span&gt;
                  &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
                  &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;attribute&quot;&gt;@val&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;attribute&quot;&gt;@actual_val&lt;/span&gt;
                &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
                &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;ok&lt;/span&gt;
              &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
        
              &lt;span class=&quot;keyword&quot;&gt;def &lt;/span&gt;&lt;span class=&quot;method&quot;&gt;failure_message&lt;/span&gt;
                &lt;span class=&quot;punct&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;The xpath &lt;span class=&quot;expr&quot;&gt;#{@xpath}&lt;/span&gt; did not have the value '&lt;span class=&quot;expr&quot;&gt;#{@val}&lt;/span&gt;'
        It was '&lt;span class=&quot;expr&quot;&gt;#{@actual_val}&lt;/span&gt;'&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;&amp;quot;&lt;/span&gt;
              &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
        
              &lt;span class=&quot;keyword&quot;&gt;def &lt;/span&gt;&lt;span class=&quot;method&quot;&gt;description&lt;/span&gt;
                &lt;span class=&quot;punct&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;match the xpath expression &lt;span class=&quot;expr&quot;&gt;#{@xpath}&lt;/span&gt; with &lt;span class=&quot;expr&quot;&gt;#{@val}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;&amp;quot;&lt;/span&gt;
              &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
            &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
        
            &lt;span class=&quot;keyword&quot;&gt;def &lt;/span&gt;&lt;span class=&quot;method&quot;&gt;match_xpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;xpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt;
              &lt;span class=&quot;constant&quot;&gt;MatchXpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;xpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
        
            &lt;span class=&quot;comment&quot;&gt;# checks if the given xpath occurs num times&lt;/span&gt;
            &lt;span class=&quot;keyword&quot;&gt;class &lt;/span&gt;&lt;span class=&quot;class&quot;&gt;HaveNodes&lt;/span&gt;  &lt;span class=&quot;comment&quot;&gt;#:nodoc:&lt;/span&gt;
              &lt;span class=&quot;keyword&quot;&gt;def &lt;/span&gt;&lt;span class=&quot;method&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;xpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;num&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;attribute&quot;&gt;@xpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;xpath&lt;/span&gt;
                &lt;span class=&quot;attribute&quot;&gt;@num&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;num&lt;/span&gt;
              &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
        
              &lt;span class=&quot;keyword&quot;&gt;def &lt;/span&gt;&lt;span class=&quot;method&quot;&gt;matches?&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;attribute&quot;&gt;@response&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;response&lt;/span&gt;
                &lt;span class=&quot;ident&quot;&gt;doc&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;is_a?&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;REXML&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;Document&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;REXML&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;Document&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;attribute&quot;&gt;@response&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;ident&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;REXML&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;XPath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;doc&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;attribute&quot;&gt;@xpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;attribute&quot;&gt;@num_found&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;size&lt;/span&gt;
                &lt;span class=&quot;attribute&quot;&gt;@num_found&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;attribute&quot;&gt;@num&lt;/span&gt;
              &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
        
              &lt;span class=&quot;keyword&quot;&gt;def &lt;/span&gt;&lt;span class=&quot;method&quot;&gt;failure_message&lt;/span&gt;
                &lt;span class=&quot;punct&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;Did not find expected number of nodes &lt;span class=&quot;expr&quot;&gt;#{@num}&lt;/span&gt; in xpath &lt;span class=&quot;expr&quot;&gt;#{@xpath}&lt;/span&gt;
        Found &lt;span class=&quot;expr&quot;&gt;#{@num_found}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;&amp;quot;&lt;/span&gt;
              &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
        
              &lt;span class=&quot;keyword&quot;&gt;def &lt;/span&gt;&lt;span class=&quot;method&quot;&gt;description&lt;/span&gt;
                &lt;span class=&quot;punct&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;match the number of nodes &lt;span class=&quot;expr&quot;&gt;#{@num}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;&amp;quot;&lt;/span&gt;
              &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
            &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
        
            &lt;span class=&quot;keyword&quot;&gt;def &lt;/span&gt;&lt;span class=&quot;method&quot;&gt;have_nodes&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;xpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;num&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt;
              &lt;span class=&quot;constant&quot;&gt;HaveNodes&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;xpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;num&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
        
          &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
        &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
        
        &lt;/pre&gt;
        
        &lt;p&gt;The first matcher &lt;code&gt;HaveXPath&lt;/code&gt; was pretty much the original I found on
        the net, it simply checks that an XPath exists, I don't use this one.&lt;/p&gt;
        
        &lt;p&gt;The next matcher &lt;code&gt;MatchXPath&lt;/code&gt; is more like what I was using in Java,
        it gets an element from the xpath and checks the value is equal to the
        one expected string. I will eventually add regex matching and arrays
        of strings or regexs to check against.&lt;/p&gt;
        
        &lt;p&gt;The last one &lt;code&gt;HaveNodes&lt;/code&gt; I find handy to make sure a given xpath matches the
        expected number of nodes.&lt;/p&gt;
        
        &lt;p&gt;I even wrote an rspec to check the matchers, and this also is handy to
        show the way to use them.&lt;/p&gt;
        
        &lt;p&gt;Note that you can pass the matchers a String containing the XML or (much faster)
        a REXML::Document.&lt;/p&gt;
        
        &lt;pre&gt;&lt;span class=&quot;ident&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;matchers&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;'&lt;/span&gt;
        
        &lt;span class=&quot;ident&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;test matchers&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;&amp;quot;&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
          &lt;span class=&quot;ident&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;symbol&quot;&gt;:each&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
            &lt;span class=&quot;attribute&quot;&gt;@xml&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;EOFXML&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;
            &amp;lt;?xml version='1.0'?&amp;gt;
              &amp;lt;claims&amp;gt;
                &amp;lt;testnode1&amp;gt;
                  &amp;lt;day&amp;gt;
                    &amp;lt;rank order='1' value='0' userid='26' alias='user25'/&amp;gt;
                    &amp;lt;rank order='2' value='0' userid='93' alias='user92'/&amp;gt;
                    &amp;lt;rank order='3' value='0' userid='55' alias='user54'/&amp;gt;
                    &amp;lt;sometext&amp;gt;this is text&amp;lt;/sometext&amp;gt;
                  &amp;lt;/day&amp;gt;
                &amp;lt;/testnode1&amp;gt;
              &amp;lt;/claims&amp;gt;
        &lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;    EOFXML&lt;/span&gt;
        
            &lt;span class=&quot;attribute&quot;&gt;@doc&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;REXML&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;Document&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;attribute&quot;&gt;@xml&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt;
        
          &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
        
          &lt;span class=&quot;ident&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;should test xpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;&amp;quot;&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
            &lt;span class=&quot;attribute&quot;&gt;@xml&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;should&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;have_nodes&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&amp;quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;/claims/*&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;&amp;quot;,&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;attribute&quot;&gt;@doc&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;should&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;have_nodes&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&amp;quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;/claims/*&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;&amp;quot;,&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;attribute&quot;&gt;@doc&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;should&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;have_nodes&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&amp;quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;/claims/testnode1/day/rank&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;&amp;quot;,&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;attribute&quot;&gt;@doc&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;should&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;have_xpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&amp;quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;/claims/testnode1/day/rank[@order='1']&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;&amp;quot;)&lt;/span&gt;
            &lt;span class=&quot;attribute&quot;&gt;@doc&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;should_not&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;have_xpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&amp;quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;/claims/testnode1/day/rank[@order='10']&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;&amp;quot;)&lt;/span&gt;
            &lt;span class=&quot;attribute&quot;&gt;@doc&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;should&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;match_xpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&amp;quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;/claims/testnode1/day/rank[1]/@order&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;&amp;quot;,&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;&amp;quot;)&lt;/span&gt;
            &lt;span class=&quot;attribute&quot;&gt;@doc&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;should&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;match_xpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&amp;quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;/claims/testnode1/day/rank[2]/@value&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;&amp;quot;,&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;&amp;quot;)&lt;/span&gt;
            &lt;span class=&quot;attribute&quot;&gt;@doc&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;should&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;match_xpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&amp;quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;/claims/testnode1/day/rank[3]/@alias&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;&amp;quot;,&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;user54&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;&amp;quot;)&lt;/span&gt;
            &lt;span class=&quot;attribute&quot;&gt;@doc&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;ident&quot;&gt;should&lt;/span&gt; &lt;span class=&quot;ident&quot;&gt;match_xpath&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;(&amp;quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;/claims/testnode1/day/sometext&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;&amp;quot;,&lt;/span&gt; &lt;span class=&quot;punct&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;this is text&lt;/span&gt;&lt;span class=&quot;punct&quot;&gt;&amp;quot;)&lt;/span&gt;
          &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
        
        &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
        &lt;/pre&gt;
        
        &lt;p&gt;&lt;a href=&quot;http://technorati.com/tag/rspec+xpath&quot; rel=&quot;tag&quot;&gt;&lt;/a&gt;&lt;/p&gt;
      </description>
      <author>Jim Morris</author>
      <pubDate>Mon, 29 Jun 2009 00:37:33 -0700</pubDate>
      <link>http://blog.wolfman.com/articles/2008/1/2/xpath-matchers-for-rspec</link>
      <guid isPermaLink='false'>urn:uuid:f6ea0f80-0c0a-4613-b44f-a0f233d8b470</guid>
    </item>
    <item>
      <title>"XPath matchers for rspec" by Jimmy Z</title>
      <description>
        Awesome! I was just about to my own custom matchers for XPath when I decided to google for an existing matcher. This is exactly what I was wanting.
        
        Nice work!
      </description>
      <pubDate>Wed, 02 Jan 2008 14:01:54 -0800</pubDate>
      <link>http://blog.wolfman.com/posts/35#comment-175</link>
      <guid isPermaLink='false'>urn:uuid:93ef8631-3728-4b36-9b1b-befaf86fcb39</guid>
    </item>
    <item>
      <title>"XPath matchers for rspec" by Jimmy Z</title>
      <description>
        It seems REXML's XPath does not work when you are specifying attribute conditions if there is a namespace declared in the xml.
        
        I was able to fix this by adding an extra parameter to the matchers to accept a namespace hash to specify a namespace prefix.
        
        For example, if your example xml had a namespace declared on claims: 
        
        `&lt;claims xmlns='http://jimmyzimmerman.com/namespace'&gt;` 
        
        then you need a namespace prefix on your elements like so:
        
        `xml.should have_xpath(&quot;/z:claims/z:testnode1/z:day/z:rank[@order='1']&quot;, {'z' =&gt; 'http://jimmyzimmerman.com/namespace'})`
        
        Feel free to email me if you would like a patch.
      </description>
      <pubDate>Thu, 03 Jan 2008 07:22:11 -0800</pubDate>
      <link>http://blog.wolfman.com/posts/35#comment-176</link>
      <guid isPermaLink='false'>urn:uuid:bf252103-0438-4732-9b15-934992af8256</guid>
    </item>
    <item>
      <title>"XPath matchers for rspec" by Randy</title>
      <description>The github link in your update @top appears to be stale.  Meanwhile, I'll try this version :)</description>
      <pubDate>Thu, 08 Jan 2009 16:35:11 -0800</pubDate>
      <link>http://blog.wolfman.com/posts/35#comment-177</link>
      <guid isPermaLink='false'>urn:uuid:b0447ef2-2a1b-44aa-9c12-e42c72e07762</guid>
    </item>
    <item>
      <title>"XPath matchers for rspec" by wolfmanjm</title>
      <description>I actually have a version I rewrote using hpricot, I'll post that too</description>
      <pubDate>Thu, 08 Jan 2009 18:18:43 -0800</pubDate>
      <link>http://blog.wolfman.com/posts/35#comment-178</link>
      <guid isPermaLink='false'>urn:uuid:34ef51e4-d888-4336-b6d7-846c290adeaf</guid>
    </item>
    <item>
      <title>"XPath matchers for rspec" by Johnathon Wright</title>
      <description>
        There is a bug in MatchXpath... for invalid xpaths, REXML::XPath.each(doc, @xpath) results in an empty set, so the value ok never gets a chance to be set to false. (I'm assuming this is not the expected behavior... )
        
        I'm on the road so I can't commit to github right now. Until I can, here are some failing specs...
        
        require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
        
        describe Spec::Matchers::MatchXpath do
        
          it 'should produce a list of nodes at the given xpath' do
            matcher = Spec::Matchers::MatchXpath.new('/ride/info', 'x')
            matcher.xml = '&lt;ride&gt;&lt;info&gt;x&lt;/info&gt;&lt;info&gt;y&lt;/info&gt;&lt;/ride&gt;'
            matcher.node_values.should == ['x', 'y']
          end
        
          describe 'when matching a path that does not exist' do
         
            before(:all) do
              value = '2009-09-09T09:09:00+09:00'
              @document = &quot;&lt;ride&gt;&lt;end_time&gt;#{value}&lt;/end_time&gt;&lt;/ride&gt;&quot;
              @matcher = Spec::Matchers::MatchXpath.new('/invalid/end_time', value)
              @matcher.matches?(@document)
            end
            
            it 'should not match' do
              @matcher.matches?(@document).should be_false
            end
        
            it 'should indicate that there were no nodes in the failure message' do
              @matcher.failure_message.should == 'There were no nodes at /invalid/end_time'
            end
        
          end
        
          describe 'with a valid xpath and one matching node of many' do
            before(:all) do
              @document = &quot;&lt;ride&gt;&lt;info&gt;a&lt;/info&gt;&lt;info&gt;b&lt;/info&gt;&lt;/ride&gt;&quot;
              @matcher = Spec::Matchers::MatchXpath.new('/ride/info', 'a')
              @matcher.matches?(@document)
            end
        
            it 'should match' do
              @matcher.matches?(@document).should be_true
            end
        
          end
        end
        
        
        and a new version of the class.....
        
            class MatchXpath
              def initialize(xpath, val)
                @xpath = xpath
                @val= val
              end
        
              def document
                @xml.is_a?(REXML::Document) ? @xml : REXML::Document.new(@xml)
              end
        
              def nodes
                REXML::XPath.match(document, @xpath)
              end
        
              def node_values
        	nodes.map do |node|
        	  case node
                  when REXML::Attribute
                    node.to_s
                  when REXML::Element
                    node.text
                  else
                    node.to_s
                  end      
        	end
              end
        
              def any_nodes_there?
                (node_values.size &gt; 0 )
              end
        
              def matching_nodes?
                node_values.any?{|value| value==@val}
              end
        
              attr_accessor :xml
        
              def matches?(response)
                @xml = response
                matching_nodes?
              end
        
              def failure_message
                if matching_nodes?
                  &quot;The xpath #{@xpath} did not have the value '#{@val}'
        It was '#{@actual_val}'&quot;
        	else
        	  &quot;There were no nodes at #{@xpath}&quot;
        	end
              end
        
              def description
                &quot;match the xpath expression #{@xpath} with #{@val}&quot;
              end
            end
        
        
        enjoy.
      </description>
      <pubDate>Tue, 02 Jun 2009 13:18:36 -0700</pubDate>
      <link>http://blog.wolfman.com/posts/35#comment-252</link>
      <guid isPermaLink='false'>urn:uuid:a1e8f553-8b6c-4b39-a2e9-dc27404aa408</guid>
    </item>
  </channel>
</rss>
