A better assert_select (assert_elements)

I've been using Rails since the days of assert_tag (an abomination that required far too much work to do very must testing for specific tags), which was powerful enough to take a look at the responses to gets and posts and make sure that they fit basic criteria.

Unfortunately, its syntax was far too verbose. Here's an actual example from the Rails API:

assert_tag(
  tag: "div",
  ancestor: { tag: "ul" },
  parent: { tag: "li", attributes: { class: "enum" } }
  descendant: { tag: "span", child: /hello world/ })

As you can see, this is no fun, so I was really happy to see assert_select come into the picture, first as a plugin, and finally getting into the Rails core in Rails 1.2. Here's what it does:

It's pretty cool, but it also has a two major drawbacks:

First, it makes a fundamentally wrong assertion about how it handles blocks. It tests all assert_selects in the block against the collection of elements matched by the initial selection. What that means is that if you want to test that at least one div on the page contains exactly two divs inside it, the basic block technique will fail:

assert_select "div" do
  assert_select "div", :count => 2
end

This will fail if you have two divs on the page each containing two divs. That's because the inner assert is being run against the set of all elements matched by the first assert_select "div", which will find four elements (two divs for each matched div).

What you want instead is for assert_select to work if and only if the inner asserts pass for at least one of the matched divs.

The second drawback is that the plugin uses its own home-grown selector system, which is both slower than hpricot and, more importantly, more limited than hpricot. It uses CSS2 selectors, and has no support for things like ancestor selection (like the original assert_tag has).

Hpricot, on the other hand, supports full CSS3 as well as some basic XPath (pretty much the same stuff that jQuery supports; it seems to have started off as a Ruby port of jQuery's selector engine).

Thankfully, Luke Redpath already did the initial work to make it possible to test Rails views with hpricot. He made it possible to do stuff like (his examples):

That's really cool, and I was strongly considering leaving it at that and using Luke's plugin, but there were some really cool things you could do with assert_select that you cannot do as easily with Luke's plugin.

So I built on Luke's plugin to create a new assert, called assert_elements:

Known Issues:

$ gem install hpricot --source http://code.whytheluckystiff.net

You can get assert_elements as a plugin at http://tools.assembla.com/svn/assert_elements/trunk/assert_elements/ by doing:

$ script/plugin install http://tools.assembla.com/svn/assert_elements/trunk/assert_elements/

Very many thanks to Luke Redpath. This plugin build very much on the work he did (in fact, most of the source excepting the assert_elements method itself and some minor changes to support blocks is Luke's!).