Start The World's Best Introduction to TDD... free!

Comments

One of my faithful readers at https://www.jbrains.ca told me that he couldn’t find an RSS feed icon in his Firefox address bar. I thought I could implement that fairly easily, so I agreed to do it. In the process, I wrote this spec:

context "when looking at the address bar" do
it "should display an RSS feed icon" do
# While I can't verify this directly, I can use
# the information at http://bit.ly/abHZ9G as a
# proxy for verifying this.
template.stub!(:logged_in?).and_return(true)
assigns[:postings] = []
render "/postings/index", :layout => true
# How do I check that the RSS link tag has the correct @href attribute?
end
end
view raw gistfile1.rb hosted with ❤ by GitHub

Ruby follows the Principle of Least Surprise, so I tried this:

response.should have_tag(%Q(link[rel="alternate"][type="application/rss+xml"][title="RSS 2.0"])) do
with_attribute(:href => rss_url)
end
view raw gistfile1.rb hosted with ❤ by GitHub

That doesn’t work, as I found no #with_attribute nor anything like it. After I dug a little, I found out that I should write this:

response.should have_tag(%Q(link[rel="alternate"][type="application/rss+xml"][title="RSS 2.0"][href=?]), rss_url)
view raw gistfile1.rb hosted with ❤ by GitHub

I don’t like this, because it checks two things at once: it looks for an “RSS tag” and checks the href attribute. That HTML implemented the “RSS tag” as attributes on <link> creates the confusion. The extremist in me calls this an integrated test, but I prefer not to go there today. Suffice it to say that when this check failed, I didn’t immediately know why, and I insist on immediately knowing why a check fails. When I’ve done this in the path with XPath assertions in XHTMLUnit or XMLUnit, I’ve resorted to writing duplicate checks that build on one another, so I tried that here:

response.should have_tag(%Q(link[rel="alternate"][type="application/rss+xml"][title="RSS 2.0"]))
response.should have_tag(%Q(link[rel="alternate"][type="application/rss+xml"][title="RSS 2.0"][href=?]), rss_url)
view raw gistfile1.rb hosted with ❤ by GitHub

Here I sacrificed duplication to improve the clarity of the assertions. I just now noticed that that contradicts my usual rule that removing duplication matters more than improving clarity. I dno’t know what to say about that just yet. Even if I remove the duplicate code, I still have the duplicate check, so extracting to a method won’t really do much here. I want #with_attribute!

Not deterred, I tried introducing a custom RSpec matcher, since I don’t know what benefit that would give me, but it would benefit me somehow. When I tried to do that, RSpec told me that it couldn’t understand response.should have_tag because it couldn’t find a has_tag? on the response. I didn’t like the looks of the stack trace; I felt I would have to delve deeply into RSpec or assert_select, and I didn’t find myself in the mood to do either, so I switched to Nokogiri.

Spec::Matchers.define :have_rss_feed_at do |rss_url_as_text|
match do |response|
html = Nokogiri::HTML(response.body)
rss_link_tag = html.at(%Q(link[rel="alternate"][type="application/rss+xml"][title="RSS 2.0"]))
rss_link_tag["href"].should == rss_url_as_text
end
failure_message_for_should do |response|
"expected to find RSS feed at #{rss_url_as_text} in response:\n#{response.body}"
end
failure_message_for_should_not do |response|
"expected not to find RSS feed at #{rss_url_as_text} in response:\n#{response.body}"
end
description do
"have an RSS feed at #{expected}"
end
end
view raw gistfile1.rb hosted with ❤ by GitHub

Here I got to write the spec the way I wanted to: assume you will find “RSS feed tag”, then check that its href attribute matches the right URL. If the response has no “RSS feed tag” at all, then complain violently, because of the higher severity of the mistake.

Of course, if you have another suggestion, I’d like to see it. Add your comment below.

Special thanks to Frivolog for helping me get the original have_tag code working.

Comments