Peter Marklund

Peter Marklund's Home


Rails Testing: Making assert_select XML safe

I've been using the new assert_select command extensively in my tests lately and it's a wonderfully powerful tool - a huge improvement over assert_tag. The only annoyance has been the warning messages "ignoring attempt to close form with link" that have been polluting my test output. The other day I decided to track the source of the messages and I discovered that others had already complained about them. I found that the root cause of the problem lies in the HTML::Document and the HTML::Tag classes that ship with Rails. More specifically the problem is in the HTML::Tag#childless? method that auto-closes HTML tags such as img, br, and hr.

What do I mean by auto-closing. Well, in XML you have to close all tags, either with a trailing slash, or with a closing tag. In HTML however, we use tags such as <br> and <link>, usually without a trailing slash. The HTML::Tag class takes those special tags into account and understands that they are auto-closing (childless) without the trailing slash. This becomes a problem if you have an XML document that contains <link>some content</link> since the parser will think the first tag is self-closing and the second tag will be a mismatch. Not only does this cause warning or error messages, but it breaks assert_select for those specific tags.

I have submitted a patch that makes HTML::Document enter XML mode if the document has an <?xml declaration. Jamis has posted about his approach which he calls assert_xml_select. However, since all my documents are XML (XHTML or VoiceXML in my current project) I chose the following approach instead which allows me to use assert_select:

if RAILS_ENV == 'test'
  module ActionController #:nodoc:
    module TestProcess
      # Work around Rails ticket
      def html_document
        @html_document ||=, true, true)

Setting the first boolean argument to true makes the parser error out if there is an unclosed or mis-matched tag. I want my documents to be valid and I like to fail early.

2 comment(s)


Anonymous said 2009-09-15 19:02:

I've also come across this annoyance with inline javascript codeblocks that contains a for loop. The less than (<) in the loop is being treated as an opening tag e.g.: <script type="text/javascript"> function foo() { for(var i=0;i<length;i++) { //do something } } </script> gives "ignoring attempt to close length with body"

Randy said 2008-02-13 13:17:

Hi Mark, I am coming across this problem but I think I know why. I am using rspec and mocking some models with mock_model(OrgSpecific::ExcurrActivity, :null_object => true) From the rspec documentation: "Setting this to true instructs the mock to ignore (quietly consume) any messages it hasn’t been told to expect – and return itself." When returning itself, it looks something like #<Spec::Mocks::Mock:0x1993260> and it tries to use that to close a tag. It will turn supposedly valid xhtml/xml into invalid xhtml/xml. Not sure what this post is supposed to accomplish, but it may be an explanation for all of those people that were having issues. I'm still working on a way around it.