Peter Marklund

Peter Marklund's Home

Sat March 24, 2007
Programming

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 http://dev.rubyonrails.org/ticket/1937
      def html_document
        @html_document ||= HTML::Document.new(@response.body, true, true)
      end
    end
  end
end

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.

Comments

Randy said over 6 years ago:

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.

--------------------------------------------------------------------------------

Anonymous said over 5 years ago:

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"

--------------------------------------------------------------------------------