The maze book for programmers!
mazesforprogrammers.com

Algorithms, circle mazes, hex grids, masking, weaving, braiding, 3D and 4D grids, spheres, and more!

DRM-Free Ebook

The Buckblog

assorted ramblings by Jamis Buck

Testing your views

29 January 2007 — 2-minute read

Here’s a quick little “advice” tip: don’t use assertions to test the structure of your views.

That is to say, don’t do this:

1
2
3
4
5
#make sure the text field is in the table cell
assert_select "table td input[type=text]"

# make sure the person's name is in the h2 header tag
assert_select "h2", person.name

Why not? Because you want your views to be very fluid. You want to keep the cost of change is so low that you have no hesitation to jump in and rearrange things to make the view cleaner. If you are using explicit tag names in your tests, you reduce that fluidity. Your views become rigid, because your tests imply that using anything but a table to format your form is wrong. Want to use an h1 for the person’s name, or a div? Don’t you dare, it’ll break the tests.

The better way to test your views is to think about what you are really wanting to test. First of all, don’t test static content, like table structures and the order of form fields on a page. Instead, test the dynamic parts of your view, especially those parts that are subject to conditional rendering. Secondly, test semantically, not syntactically. That is to say, don’t base a test on the type of the tag, but rather on what you want the content to represent. Use CSS classes and DOM id’s instead of explicit tag names.

Here’s a concrete example. Suppose you have a view like this:

1
2
3
4
5
<% if @user.administrator? %>
  Hi <%= @user.name %>! You appear to be an administrator.
  <%= link_to "Click here", admin_url, :id => "admin_link" %>
  to see the admin stuff!
<% end %>

The only really significant thing you ought to be testing here is that the admin link only shows up for administrators. You might also want to test that the link points to where you expect it to, though that’s a lower importance.

1
2
3
4
5
6
7
8
9
10
11
def test_admin_sees_link
  # set up session for an admin user first, and then:
  get "index"
  assert_select "#admin_link"
end

def test_non_admin_does_not_see_link
  # set up session for a normal, non-admin user first, and then:
  get "index"
  assert_select "#admin_link", false
end

Arranging your tests like this will make them less likely to break on cosmetic tweaks, which will increase your confidence in your tests and your willingness to tweak your views.

Reader Comments

Thanks for the quick tip on testing your views. Perhaps I should muck around more with the rSpec on Rails view specifications. This comes in handy when you’re HTML/CSS designers are working in the views, moving things around… and you can have them run their tests (or specs) before they commit to verify that they didn’t break any of the logic within the views.

assert_select is so nifty. Please write more about it… I’m sure there’s stuff I’ve missed.

yehuda katz has an interesting version of assert_select that uses hpricot :

http://yehudakatz.com/2007/01/27/a-better-assert_select-assert_elements/

weepy

You’re absolutely right.

If you want to get a handle on testing views, just think of the way you would style them, such that you could change the style without having to change the code. Not all elements you test will be styled explicitly, but the same principles apply. If you do that, you’ll get both content and tests to survive layout changes.

Having done CSS for awhile, this is almost second nature, that translates well to test cases. If you wanted to style person.name, would you look for an H2 or a .person.name?

I always look by ID first, class second, composition last if at all, and only on specific cases actually at the element type (e.g. input fields).

One thing I like to do is separating functional and layout tests.

If I’m testing a blog, I use the functional tests to make sure a new post will show up, and disappear if I change its status back to draft. These tests are about the logic of the application, not the layout, and shouldn’t break when you change the layout.

I use the layout tests to make sure it looks the way it’s expected to. So for a blog, I would check that all the posts appear (by ID), but also that they appear in reverse chronological order (composition). And I would test it against my stylesheets. Since I’m using hAtom, I know the title will show up as such if it has the .entry-title class and contained inside an .hentry.

Of course, I could just render the page in the browser, but when you get to have enough pages with different content combinations, manually testing each one of them is a recipe for failure.

So essentially, I would recommend checking each view twice. Once against the functional behavior of the app: results come from actions. And once against the presentational spec: that changes to the code do not break the users.

And always look for the least amount of information to identify an element, in both your CSS stylesheets and test cases. The less information you need, the more change resilient they are.

The only thing with that is that you may not need/want an id for that specific link however if it is safe to assume you want the link to appear in a #nav div, is there any way to do that.

Worded slightly differently, what I’m trying to say is, is there a way to assert that a link pointing to ’/foo/bar/’ exists in a div of id #nav without having to give the link an id itself?

As fluid as i want to be, i’ve been bitten several times when the template changes has restructured the form, etc, but the test and controller were still based on old expected params[] – wrong sense of security when tests all passes

So now, i use hpricot_forms: http://www.agilewebdevelopment.com/plugins/hpricot_forms

Based on form field names. Not css. Not id.

Jamie, assert_select uses CSS selectors, so in your case you can do something like:

assert_select ”#nav a[href$=#{home_path}]”

And that will check that you have an a tag, which href attribute ends in what results from evaluating `home_path`, and which has an ancestor with id “nav”.

For more information on CSS selectors and assert_select you can check assert_select’s cheat sheet at http://blog.labnotes.org/2006/09/04/assert_select-cheat-sheet/

It has certainly proved to be useful to me :)

What Nicolas said.

You can also use substitution values. The substituted value can be a string or regexp:

assert_select ”#nav div a[href=?]”, url

Choonkeat, you’re absolutely right. If you’re testing a form, checking the ID doesn’t guarantee you’ll be able to submit it. Look for the field name, something like:

assert_select “form[action=/login]” do assert_select “inputname=username” end