assert_select plugin for Rails

logo.jpg

Update: The new release of assert_select includes support for CSS pseudo classes (nth-child, first-child, empty). More details here. It also supports nested assertions for dealing with lists, tables and forms. Some examples here. I updated this post to use nested asserts.

Update 2:A new release adds support for RJS.

Update 3: assert_select is now part of Rails core. Also, cheat sheet available here.

assert_select helps you write functional tests using CSS selectors.

If you’re using HTML and CSS, you’re already familiar with CSS selectors. CSS selectors have a simple syntax that lets you pick parts of the page and apply styling. assert_select uses the same simple syntax, for testing the content of the page.

You can test that selected element(s) exist, has specific text content, test number and order of elements, and a few more tricks.

Here’s a simple example that asserts the page has the right title:

det test_page_has_right_title
  get :index
  assert_select "title", "Welcome"
end

And a more complex one that tests a login form:

def test_login_form_has_all_fields
  get :login
  assert_select "form[action=http://myapp/login] input" do |inputs|
    assert_equal 3, inputs.size
    assert_select "input[type=name][name=username]"
    assert_select "input[type=password][name=password]"
    assert_select "input[type=submit][value=Login]"
  end
end

You can also use css_select to pick specific elements and work with them. This example asserts that the page header has all the right links:

HEADER_LINKS = [
  ["Main", "http://myapp/"],
  ["About", "http://myapp/about"],
  ["Login", "http://myapp/login"]
]

def test_page_header_links
  assert_select "#header>ul>li>a" do
    HEADER_LINKS.each do |text, href|
      assert_select "a[href=?]", href, text
    end
  end
end

Here’s a few more examples:

# Form includes four input fields
assert_select "form input", 4

# Page does not have any forms in it.
assert_select "form", false, "Page must contain no forms"

# Page has one link back to user's page.
assert_select "a[href=?]", url_for(:controller=>"user", :id=>user_id),
             :count=>1, :text=>"Back to page"

The last example uses substituation. The question mark gets replaced with an argument, in this case the result of url_for. You can use strings and regular expressions.

To install assert_select:

./script/plugin install http://labnotes.org/svn/public/ruby/rails_plugins/assert_select

As always, code is under MIT and/or Creative Commons license. Enjoy!

26 thoughts on “assert_select plugin for Rails

  1. Pingback: Write functional tests in Rails using CSS selectors

  2. Pingback: InfoHatter Blog! :: assert_select Plugin Allows you to Run Funtional Tests on your .rhtml

  3. Pingback: TDD juice

  4. Pingback: Labnotes » Blog Archive » The UI is the API: Scraping with Ruby

  5. Hey Assif,
    Great plugin. I ran into what might be a bug, though.

    When checking for a form’s existence like this:
    assert_select “form[action=/job/find_customer_account_number_by_name]”

    All is well, and it acts as expected. However, after that things start failing. For example, both if these lines work when used without calling that form’s assert_select before hand:

    assert_tag :tag => “table”, :attributes => {:id => ‘customer_account_numbers’}

    assert_select “#customer_account_numbers”

    But if I do make that first assert_select call, these two lines don’t work. For example, the last assert_select returns the following:

    No match made with selector #.
    is not true.

    Any ideas? This is in an integration test, if it makes a difference.

    Thanks!

  6. Here’s the whole chunk, if it helps:

    # Hit the job index and get redirected, because we’re not logged in.
    get ‘/job/index’
    assert_redirected_to(:controller => ‘user’, :action => ‘login’)
    # Log in, and go back to the Job index.
    post_via_redirect ‘/user/login’, :user => {:login => ‘admin’, :password => ’12345′}
    assert_select “form[action=/job/receive]”
    #assert_select “legend#create_job_legend”
    # Look up customer’s by name
    post ‘/job/find_customer_account_number_by_name’, :find_customer_account_number_by_name => {:name => ‘Meg’}
    assert_response :success
    #assert_tag :tag => “table”, :attributes => {:id => ‘customer_account_numbers’}
    assert_select “#customer_account_numbers”
    assert_select “legend#create_job_legend”

  7. Doug,

    I didn’t try it with integration tests, only functional tests.

    One tip, if you’re routes are tested, you might as well use url_for, so from your example:

    assert_select “form[action=?]“, url_for(:controller=>”job”, :action=>”receive”)

  8. Pingback: Gluttonous : assert_select included in core, assert_tag deprecated

  9. Any chance you might want to use why’s Hpricot for parsing the CSS selectors? You’ll get to use XPath notation for selects for free too!

  10. Kamal,

    assert_select supports CSS 3 selectors, which have a lot of functionality for selecting content in HTML. The expressions are easier to read than XPath and much better at handling HTML as content.

    Not to mention, those are the same CSS selectors I use when styling the page.

  11. Pingback: a work on process » Rails flash tests deprecated?

  12. Thanks Assaf, awesome stuff! Just a note for your other readers is that assert_select and arts cannot coexist in the same project… Didn’t bother to figure out why, just removed the plugin and off I went with assert_select.

    Keep up the good work

  13. Kenneth,

    Too bad, I like Arts. It has some overlap with assert_select but also assertions that do other interesting things. I’ll contact the author and see what we can do.

  14. Kenneth,

    I just tried assert_select with arts in the same project and I’m not seeing any problems, neither is Kevin. If you’re seeing any problems, let us know.

    For those who don’t know, Arts is a great plugin for testing your RJS.

  15. Joe,

    Good question. I haven’t used integration tests yet, but from what I know the get/post will return a response body, so it will work with assert_select.

  16. Assaf,

    I tried using it, however I get an error… Maybe you could try it on your end to see if it’s something that could be easily changed so it works with integration tests… Here’s the error I get in case this sheds any light:

    NoMethodError: You have a nil object when you didn’t expect it!
    The error occured while evaluating nil.body
    /usr/local/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_controller/test_process.rb:419:in `html_document’
    /Users/joe/dev/rails/quikauctions/stable1.0/config/../vendor/plugins/assert_select/lib/assert_select.rb:141:in `assert_select’
    /usr/local/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_controller/integration.rb:523:in `method_missing’
    test/integration/calendar_test.rb:74:in `test_gateway_change_clears_calendar’
    /usr/local/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_controller/integration.rb:431:in `run’

  17. Just tried this, but it’s having some parsing my response. I’m using Google Maps (via ym4r_gm, for what it’s worth), and there’s a load of HTML in the where the info_windows are defined, including some divs. Obviously they’re not valid HTML — the W3c validator doesn’t like the VML hack for IE, apart from anything else — but not sure of a way around that. Suspect I’ll have to live with them being unvalid, but would be nice to be able to use assert_select. I’m using hpricot_test_helper at the moment (which is pretty good), but what with assert_select going into core…

  18. ctagg,

    There are several options for processing invalid HTML, including Tidy, Hpricot and Rubyfulsoup, all of which can clean it up.

    With scrAPI, I use Tidy to clean up the HTML before processing it, and it uses the same engine as assert_select. So you can look at the code here:
    http://labnotes.org/svn/public/ruby/scrapi/lib/scraper/reader.rb

    The parse_page method can take html_document and give you back an HTML node that you can pass to assert_select as the first argument.

  19. Works like a charm but in Firefox (1.5.0.7) the code with the plugin installation is rendered truncated, and the folder should be script/plugin and not script/plugins.

  20. Hi im really a newbie.. cud u please tell me how to install the assert_select plugin if i downloaded the .rb file to my computer.

    F:\InstantRails-1.3a-win\InstantRails\ruby\lib\ruby\gems\1.8\gems\actionpack-1.12.1\lib\action_controller

    should i add the method in the test process ???

  21. Pingback: Assertions for Synergy/DE -- Chip’s Tips for Developers

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>