24 February 2008

Readable Specifications : Hpricot and RSpec

I use FIT and HTMLUnit during the day for really, truly testing my web application. At night, when I transform myself into a Ruby monster, it's RSpec and Hpricot (the "enjoyable" HTML parser). I had tried before with assert_select, but I ended up with a zillion nested with_tag statements that didn't help my digestion.

At work, we have written a little bit of transformation so that the tables we see in our FIT tests look pretty similar to the tables we are testing in our application. I wanted to do the same with RSpec so it would look like this:

table = doc.search("//table[@id='invitations']").first

table.should_match([
  [ "email",            "sent",        "accepted",    "login"   ],
  [ "foo@bar",          "2008-02-10",  "2008-02-16",  "foobar"  ],
  [ "yoyo@toto",        "2008-02-15",  "2008-02-17",  "yoyo"    ],
  [ "will@not.accept",  "2008-02-15",  "",            ""        ],
])

The following little bit of code does the job (I dropped it into my spec_helper)

module Hpricot
  class Elem
    def should_match(array)
      array.each_with_index { |row_data, row_index|
        if (row_index == 0)
          cell_tag = "th"
          row = 1
        else
          cell_tag = "td"
          row = row_index
        end
        row_data.each_with_index { |cell_text, column_index|
          q = "//tr[#{row}]/#{cell_tag}[#{column_index + 1}]"
          self.search(q).inner_html.strip.should == cell_text
        }
      }
    end
  end
end

This is a really basic solution ... there's lots of work before I can use it to test attributes and nested markup in an equally readable way. And it assumes the first row is in a <thead> and contains <th> elements; and that the remainder is under a <tbody> element (this is how Markaby generates markup (it also happens to be correct, if you have a header row)).

I'm a total Hpricot n00b, so there's probably a better way to implement this, but I'm enjoying it for now ... please let me know if you have a better way :)

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.