Close
Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

LABS
Better View Testing with Elementor

We’ve got a few mantras at Pivotal. One of them has to do with testing all the time. It’s a Good Thing, for sure. Until recently though, I had always inserted a tacit “except for views” to the end of it. The reason for my reservations wasn’t the fact that view tests can be brittle. Any test can be brittle. I didn’t like testing views because it seemed like the test I was writing never really described the code I was writing. Let’s look at a typical view test to see what I mean:

describe "/posts/index.html.erb" do
  def render_view
    render "/posts/index.html.erb"
    response.body
  end

  before(:each) do
    assigns[:posts] = [
      stub_model(Post, :name => "First!", :body => "first body."),
      stub_model(Post, :name => "Second!", :body => "second body.")
    ]
  end

  describe "assertions using have_tag" do
    it "renders posts" do
      render_view
      response.should have_tag(".post", 2)
    end

    it "renders post headers" do
      render_view
      response.should have_tag(".post .post-name", "First!", 1)
      response.should have_tag(".post .post-name", "Second!", 1)
    end

    it "renders post bodies" do
      render_view
      response.should have_tag(".post .post-body", "first body.", 1)
      response.should have_tag(".post .post-body", "second body.", 1)
    end
  end
end

This snippet uses the have_tag helper. It’s somewhat slow, and to my eyes, expresses intent about as well as an apple floating in a top-hat filled with perfume. The “tag” is a selector? The content filter is just the second argument? And the last argument is the amount of “tag” the response should have? You can test like this, but why would you?

I’ve also seen a more manual pattern, using a library like Hpricot or Nokogiri to parse the response body, then asserting on the results of that:

describe "assertions using Nokogiri" do
  def doc
    @doc ||= Nokogiri(render_view)
  end

  it "renders posts" do
    doc.search('.post').should have(2).nodes
  end

  it "renders post headers" do
    headers = doc.search('.post .post-name')
    headers.should have(2).elements
    headers.detect { |element| element.text == "First!" }.should_not be_nil
    headers.detect { |element| element.text == "Second!" }.should_not be_nil
  end

  it "renders post bodies" do
    bodies = doc.search('.post .post-name')
    bodies.should have(2).elements
    bodies.detect { |element| element.text == "first body." }.should_not be_nil
    bodies.detect { |element| element.text == "second body." }.should_not be_nil
  end
end

It’s faster, since it’s not using have_tag, but still not very expressive. CSS selectors are still littered across the it statements, but at least it’s only once per test. Still, using detect to find content is no good. And I don’t think CSS selectors have any business in it statements at all. That seems like asserting on the name of a method being called, not its behavior.

The solution!

Given my problems with the above approaches, I created a gem that allows the following assertion syntax:

it "renders posts" do
  result.should have(2).posts
end

it "renders post headers" do
  result.should have(2).post_headers
  result.should have(1).post_header.with_text("First!")
  result.should have(1).post_header.with_text("Second!")
end

it "renders post bodies" do
  result.should have(2).post_bodies
  result.should have(1).post_body.with_text("first body.")
  result.should have(1).post_body.with_text("second body.")
end

What’s a result? And how does it know how many posts, post_headers, and post_bodies it has? The result is defined in a before block like so:

require 'elementor'
require 'elementor/spec'

include Elementor

attr_reader :result

before(:each) do
  @result = elements(:from => :render_view) do |tag|
    tag.posts         ".post"
    tag.post_headers  ".post .post-name"
    tag.post_bodies   ".post .post-body"
  end
end

The elements method allows you to name your CSS selectors using the tag block argument. The tag object uses method_missing to register your names. The :from option specifies a method to be called that will return some raw markup.

Naming selectors alone was a huge win for me, but there are a few other cool bits about the @result object. First, you get to use the with_text helper for filtering content. You’ll also get a with_attrs helper for filtering based on a hash of attribute values.

The project is called Elementor, and you can install it like so:

[sudo] gem install elementor

The code is on the GitHub here: github.com/nakajima/elementor (and you can see the CI build here). Take a look at the specs for all of the examples of what you can do. Hopefully, you’ll find it as useful as I have. If not, please share your reasons in the comments!

Comments
  1. Kyle says:

    Sounds like it would work well with [Selector Gadget](http://www.selectorgadget.com), a free css inspector bookmarklet [Andrew](http://andrewcantino.com) and I wrote.

  2. Pat Maddox says:

    Cool. View testing doesn’t look quite so evil

    p.s. I hate captchas but I love cereal!

  3. This is cool, but why not just bite the bullet and go semantic/CSS all the way? Eg, like jQuery. You’re most of the way there already anyway.

    eg

    response.should_match(“.posts”).twice
    response.should_match(“.posts#first”).once
    response.should_match(“.posts#last”).once

  4. Sorry. I hate Markdown and Textile.

    response.should_match(“.posts”).twice

    response.should_match(“.posts#first”).once

    response.should_match(“.posts#last”).once

  5. Pat Nakajima says:

    @giles,

    My primary goal for this project was to decouple CSS selectors from my view tests. Keeping all of the CSS within the `elements` call has let me refactor my CSS with less hassle, since I don’t have to scan through a bunch of `it` statements for tests broken by the changes. Plus, to my eyes it’s prettier this way, with Ruby doing most of the describin’.

    One more thing: the fact that my code for this project is just a rather thin wrapper around Nokogiri meant that I didn’t really have to fiddle with writing RSpec matchers, which I didn’t really care to do. Besides writing a bit of adapter code for the `have` matcher, Elementor works with test/unit, or even sans testing framework.

Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *