<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/css" href="/stylesheets/rss.css"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">
  <channel>
    <title>Wolfmans Howlings: XPath matchers for rspec</title>
    <link>http://blog.wolfman.com/articles/2008/01/02/xpath-matchers-for-rspec</link>
    <language>en-us</language>
    <ttl>40</ttl>
    <description>A programmers Blog about Ruby, Rails and a few other issues</description>
    <item>
      <title>XPath matchers for rspec</title>
      <description>&lt;p&gt;&lt;em&gt;UPDATE&lt;/em&gt; I found
&lt;a href="http://github.com/kamal/rspec_hpricot_matchers/tree/master"&gt;this&lt;/a&gt;
which is a nicer way of doing it using hpricot, which is faster. I
have created a slightly different version that does have_xpath, for XML.&lt;/p&gt;

&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="http://code.google.com/p/hamcrest"&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="http://gensym.org/2007/7/15/custom-xpath-matcher-for-rspec"&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;div class="typocode"&gt;&lt;pre&gt;&lt;code class="typocode_ruby "&gt;
&lt;span class="ident"&gt;require&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span class="string"&gt;rexml/document&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;
&lt;span class="ident"&gt;require&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span class="string"&gt;rexml/element&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;

&lt;span class="keyword"&gt;module &lt;/span&gt;&lt;span class="module"&gt;Spec&lt;/span&gt;
  &lt;span class="keyword"&gt;module &lt;/span&gt;&lt;span class="module"&gt;Matchers&lt;/span&gt;

    &lt;span class="comment"&gt;# check if the xpath exists one or more times&lt;/span&gt;
    &lt;span class="keyword"&gt;class &lt;/span&gt;&lt;span class="class"&gt;HaveXpath&lt;/span&gt;
      &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;initialize&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;xpath&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
        &lt;span class="attribute"&gt;@xpath&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;xpath&lt;/span&gt;
      &lt;span class="keyword"&gt;end&lt;/span&gt;

      &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;matches?&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;response&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
        &lt;span class="attribute"&gt;@response&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;response&lt;/span&gt;
        &lt;span class="ident"&gt;doc&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;response&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;is_a?&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="constant"&gt;REXML&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;Document&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt; &lt;span class="punct"&gt;?&lt;/span&gt; &lt;span class="ident"&gt;response&lt;/span&gt; &lt;span class="punct"&gt;:&lt;/span&gt; &lt;span class="constant"&gt;REXML&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;Document&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;new&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="attribute"&gt;@response&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
        &lt;span class="ident"&gt;match&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="constant"&gt;REXML&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;XPath&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;match&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;doc&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="attribute"&gt;@xpath&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
        &lt;span class="keyword"&gt;not&lt;/span&gt; &lt;span class="ident"&gt;match&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;empty?&lt;/span&gt;
      &lt;span class="keyword"&gt;end&lt;/span&gt;

      &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;failure_message&lt;/span&gt;
        &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;Did not find expected xpath &lt;span class="expr"&gt;#{@xpath}&lt;/span&gt;&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;
      &lt;span class="keyword"&gt;end&lt;/span&gt;

      &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;negative_failure_message&lt;/span&gt;
        &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;Did find unexpected xpath &lt;span class="expr"&gt;#{@xpath}&lt;/span&gt;&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;
      &lt;span class="keyword"&gt;end&lt;/span&gt;

      &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;description&lt;/span&gt;
        &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;match the xpath expression &lt;span class="expr"&gt;#{@xpath}&lt;/span&gt;&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;
      &lt;span class="keyword"&gt;end&lt;/span&gt;
    &lt;span class="keyword"&gt;end&lt;/span&gt;

    &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;have_xpath&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;xpath&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
      &lt;span class="constant"&gt;HaveXpath&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;new&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;xpath&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span class="keyword"&gt;end&lt;/span&gt;

    &lt;span class="comment"&gt;# check if the xpath has the specified value&lt;/span&gt;
    &lt;span class="comment"&gt;# value is a string and there must be a single result to match its&lt;/span&gt;
    &lt;span class="comment"&gt;# equality against&lt;/span&gt;
    &lt;span class="keyword"&gt;class &lt;/span&gt;&lt;span class="class"&gt;MatchXpath&lt;/span&gt;
      &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;initialize&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;xpath&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;val&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
        &lt;span class="attribute"&gt;@xpath&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;xpath&lt;/span&gt;
        &lt;span class="attribute"&gt;@val&lt;/span&gt;&lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;val&lt;/span&gt;
      &lt;span class="keyword"&gt;end&lt;/span&gt;

      &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;matches?&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;response&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
        &lt;span class="attribute"&gt;@response&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;response&lt;/span&gt;
        &lt;span class="ident"&gt;doc&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;response&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;is_a?&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="constant"&gt;REXML&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;Document&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt; &lt;span class="punct"&gt;?&lt;/span&gt; &lt;span class="ident"&gt;response&lt;/span&gt; &lt;span class="punct"&gt;:&lt;/span&gt; &lt;span class="constant"&gt;REXML&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;Document&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;new&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="attribute"&gt;@response&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
        &lt;span class="ident"&gt;ok&lt;/span&gt;&lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="constant"&gt;true&lt;/span&gt;
        &lt;span class="constant"&gt;REXML&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;XPath&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;each&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;doc&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="attribute"&gt;@xpath&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt; &lt;span class="punct"&gt;|&lt;/span&gt;&lt;span class="ident"&gt;e&lt;/span&gt;&lt;span class="punct"&gt;|&lt;/span&gt;
          &lt;span class="attribute"&gt;@actual_val&lt;/span&gt;&lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="keyword"&gt;case&lt;/span&gt; &lt;span class="ident"&gt;e&lt;/span&gt;
          &lt;span class="keyword"&gt;when&lt;/span&gt; &lt;span class="constant"&gt;REXML&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;Attribute&lt;/span&gt;
            &lt;span class="ident"&gt;e&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;to_s&lt;/span&gt;
          &lt;span class="keyword"&gt;when&lt;/span&gt; &lt;span class="constant"&gt;REXML&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;Element&lt;/span&gt;
            &lt;span class="ident"&gt;e&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;text&lt;/span&gt;
          &lt;span class="keyword"&gt;else&lt;/span&gt;
            &lt;span class="ident"&gt;e&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;to_s&lt;/span&gt;
          &lt;span class="keyword"&gt;end&lt;/span&gt;
          &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="constant"&gt;false&lt;/span&gt; &lt;span class="keyword"&gt;unless&lt;/span&gt; &lt;span class="attribute"&gt;@val&lt;/span&gt; &lt;span class="punct"&gt;==&lt;/span&gt; &lt;span class="attribute"&gt;@actual_val&lt;/span&gt;
        &lt;span class="keyword"&gt;end&lt;/span&gt;
        &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="ident"&gt;ok&lt;/span&gt;
      &lt;span class="keyword"&gt;end&lt;/span&gt;

      &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;failure_message&lt;/span&gt;
        &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;The xpath &lt;span class="expr"&gt;#{@xpath}&lt;/span&gt; did not have the value '&lt;span class="expr"&gt;#{@val}&lt;/span&gt;'&lt;span class="escape"&gt;\n&lt;/span&gt;It was '&lt;span class="expr"&gt;#{@actual_val}&lt;/span&gt;'&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;
      &lt;span class="keyword"&gt;end&lt;/span&gt;

      &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;description&lt;/span&gt;
        &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;match the xpath expression &lt;span class="expr"&gt;#{@xpath}&lt;/span&gt; with &lt;span class="expr"&gt;#{@val}&lt;/span&gt;&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;
      &lt;span class="keyword"&gt;end&lt;/span&gt;
    &lt;span class="keyword"&gt;end&lt;/span&gt;

    &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;match_xpath&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;xpath&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;val&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
      &lt;span class="constant"&gt;MatchXpath&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;new&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;xpath&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;val&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span class="keyword"&gt;end&lt;/span&gt;

    &lt;span class="comment"&gt;# checks if the given xpath occurs num times&lt;/span&gt;
    &lt;span class="keyword"&gt;class &lt;/span&gt;&lt;span class="class"&gt;HaveNodes&lt;/span&gt;  &lt;span class="comment"&gt;#:nodoc:&lt;/span&gt;
      &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;initialize&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;xpath&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;num&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
        &lt;span class="attribute"&gt;@xpath&lt;/span&gt;&lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;xpath&lt;/span&gt;
        &lt;span class="attribute"&gt;@num&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;num&lt;/span&gt;
      &lt;span class="keyword"&gt;end&lt;/span&gt;

      &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;matches?&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;response&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
        &lt;span class="attribute"&gt;@response&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;response&lt;/span&gt;
        &lt;span class="ident"&gt;doc&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;response&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;is_a?&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="constant"&gt;REXML&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;Document&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt; &lt;span class="punct"&gt;?&lt;/span&gt; &lt;span class="ident"&gt;response&lt;/span&gt; &lt;span class="punct"&gt;:&lt;/span&gt; &lt;span class="constant"&gt;REXML&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;Document&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;new&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="attribute"&gt;@response&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
        &lt;span class="ident"&gt;match&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="constant"&gt;REXML&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;XPath&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;match&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;doc&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="attribute"&gt;@xpath&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
        &lt;span class="attribute"&gt;@num_found&lt;/span&gt;&lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;match&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;size&lt;/span&gt;
        &lt;span class="attribute"&gt;@num_found&lt;/span&gt; &lt;span class="punct"&gt;==&lt;/span&gt; &lt;span class="attribute"&gt;@num&lt;/span&gt;
      &lt;span class="keyword"&gt;end&lt;/span&gt;

      &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;failure_message&lt;/span&gt;
        &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;Did not find expected number of nodes &lt;span class="expr"&gt;#{@num}&lt;/span&gt; in xpath &lt;span class="expr"&gt;#{@xpath}&lt;/span&gt;&lt;span class="escape"&gt;\n&lt;/span&gt;Found &lt;span class="expr"&gt;#{@num_found}&lt;/span&gt;&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;
      &lt;span class="keyword"&gt;end&lt;/span&gt;

      &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;description&lt;/span&gt;
        &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;match the number of nodes &lt;span class="expr"&gt;#{@num}&lt;/span&gt;&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;
      &lt;span class="keyword"&gt;end&lt;/span&gt;
    &lt;span class="keyword"&gt;end&lt;/span&gt;

    &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;have_nodes&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;xpath&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;num&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
      &lt;span class="constant"&gt;HaveNodes&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;new&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;xpath&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;num&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span class="keyword"&gt;end&lt;/span&gt;

  &lt;span class="keyword"&gt;end&lt;/span&gt;
&lt;span class="keyword"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div class="typocode"&gt;&lt;pre&gt;&lt;code class="typocode_ruby "&gt;&lt;span class="ident"&gt;require&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span class="string"&gt;matchers&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;

&lt;span class="ident"&gt;describe&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;test matchers&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt;
  &lt;span class="ident"&gt;before&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="symbol"&gt;:each&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt;
    &lt;span class="attribute"&gt;@xml&lt;/span&gt;&lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="punct"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="constant"&gt;EOFXML&lt;/span&gt;&lt;span class="string"&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="constant"&gt;    EOFXML&lt;/span&gt;

    &lt;span class="attribute"&gt;@doc&lt;/span&gt;&lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="constant"&gt;REXML&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;Document&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;new&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="attribute"&gt;@xml&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;

  &lt;span class="keyword"&gt;end&lt;/span&gt;

  &lt;span class="ident"&gt;it&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;should test xpath&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt;
    &lt;span class="attribute"&gt;@xml&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;should&lt;/span&gt; &lt;span class="ident"&gt;have_nodes&lt;/span&gt;&lt;span class="punct"&gt;(&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;/claims/*&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="number"&gt;1&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span class="attribute"&gt;@doc&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;should&lt;/span&gt; &lt;span class="ident"&gt;have_nodes&lt;/span&gt;&lt;span class="punct"&gt;(&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;/claims/*&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="number"&gt;1&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span class="attribute"&gt;@doc&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;should&lt;/span&gt; &lt;span class="ident"&gt;have_nodes&lt;/span&gt;&lt;span class="punct"&gt;(&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;/claims/testnode1/day/rank&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="number"&gt;3&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span class="attribute"&gt;@doc&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;should&lt;/span&gt; &lt;span class="ident"&gt;have_xpath&lt;/span&gt;&lt;span class="punct"&gt;(&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;/claims/testnode1/day/rank[@order='1']&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;)&lt;/span&gt;
    &lt;span class="attribute"&gt;@doc&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;should_not&lt;/span&gt; &lt;span class="ident"&gt;have_xpath&lt;/span&gt;&lt;span class="punct"&gt;(&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;/claims/testnode1/day/rank[@order='10']&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;)&lt;/span&gt;
    &lt;span class="attribute"&gt;@doc&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;should&lt;/span&gt; &lt;span class="ident"&gt;match_xpath&lt;/span&gt;&lt;span class="punct"&gt;(&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;/claims/testnode1/day/rank[1]/@order&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;1&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;)&lt;/span&gt;
    &lt;span class="attribute"&gt;@doc&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;should&lt;/span&gt; &lt;span class="ident"&gt;match_xpath&lt;/span&gt;&lt;span class="punct"&gt;(&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;/claims/testnode1/day/rank[2]/@value&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;0&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;)&lt;/span&gt;
    &lt;span class="attribute"&gt;@doc&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;should&lt;/span&gt; &lt;span class="ident"&gt;match_xpath&lt;/span&gt;&lt;span class="punct"&gt;(&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;/claims/testnode1/day/rank[3]/@alias&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;user54&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;)&lt;/span&gt;
    &lt;span class="attribute"&gt;@doc&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;should&lt;/span&gt; &lt;span class="ident"&gt;match_xpath&lt;/span&gt;&lt;span class="punct"&gt;(&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;/claims/testnode1/day/sometext&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;this is text&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;)&lt;/span&gt;
  &lt;span class="keyword"&gt;end&lt;/span&gt;

&lt;span class="keyword"&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="http://technorati.com/tag/rspec+xpath" rel="tag"&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <pubDate>Wed, 02 Jan 2008 13:32:50 -0800</pubDate>
      <guid isPermaLink="false">urn:uuid:f6ea0f80-0c0a-4613-b44f-a0f233d8b470</guid>
      <author>Jim Morris</author>
      <link>http://blog.wolfman.com/articles/2008/01/02/xpath-matchers-for-rspec</link>
      <category>RSpec</category>
      <category>Rails</category>
      <category>rspec</category>
      <category>xpath</category>
      <category>matcher</category>
      <trackback:ping>http://blog.wolfman.com/articles/trackback/339</trackback:ping>
    </item>
    <item>
      <title>"XPath matchers for rspec" by Jimmy Z</title>
      <description>&lt;p&gt;It seems REXML's XPath does not work when you are specifying attribute conditions if there is a namespace declared in the xml.&lt;/p&gt;

&lt;p&gt;I was able to fix this by adding an extra parameter to the matchers to accept a namespace hash to specify a namespace prefix.&lt;/p&gt;

&lt;p&gt;For example, if your example xml had a namespace declared on claims: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;claims xmlns='http://jimmyzimmerman.com/namespace'&amp;gt;&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;then you need a namespace prefix on your elements like so:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;xml.should have_xpath("/z:claims/z:testnode1/z:day/z:rank[@order='1']", {'z' =&amp;gt; 'http://jimmyzimmerman.com/namespace'})&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Feel free to email me if you would like a patch.&lt;/p&gt;</description>
      <pubDate>Thu, 03 Jan 2008 07:22:11 -0800</pubDate>
      <guid isPermaLink="false">urn:uuid:bf252103-0438-4732-9b15-934992af8256</guid>
      <link>http://blog.wolfman.com/articles/2008/01/02/xpath-matchers-for-rspec#comment-189</link>
    </item>
    <item>
      <title>"XPath matchers for rspec" by Jimmy Z</title>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Nice work!&lt;/p&gt;</description>
      <pubDate>Wed, 02 Jan 2008 14:01:54 -0800</pubDate>
      <guid isPermaLink="false">urn:uuid:93ef8631-3728-4b36-9b1b-befaf86fcb39</guid>
      <link>http://blog.wolfman.com/articles/2008/01/02/xpath-matchers-for-rspec#comment-188</link>
    </item>
  </channel>
</rss>
