---
title: time to step up
teaser: Testing views in Rails applications.
tags: web,rails,testing
author: Jared Carroll
published_on: 2007-09-16
---

I've ignored testing my views in Rails for far too long.  So I've started to try
some stuff out in my existing functional tests.

## Using `assert_select`

This is a really powerful assertion added to `Test::Unit::TestCase` that allows
you to use `CSS` selectors when testing views and is the basis for all my view
testing.

Say we have `locations`.  A `Location` object has only 2 fields:

* name - varchar
* address - text

We'll have a `LocationsController` to handle all our `Location` CRUD.

```ruby
class LocationsController < ApplicationController
end
```

Let's start out with the C in <abbr title="Create Read Update
Delete">CRUD</abbr>, Create.

Here's our functional test method for our `LocationsController#new` action.

```ruby
def test_should_create_a_new_location_object_on_GET_to_new
  get :new

  assert_kind_of Location, assigns(:location)
  assert_response :success
  assert_template 'new'

  assert_select 'form[action=?]', locations_path, true,
    'no form to locations#create was found'
  assert_select 'form input', 2,
    '2 input fields were not found within a form' do
      assert_select '[type=text][name=?]', 'location[name]', true,
        "no text field for a location's name was found within a form"
      assert_select '[type=submit][value=Create]', true,
        'no submit button was found within a form'
  end
  assert_select 'form textarea[name=?]', 'location[address]', true,
    "no textarea for a location's address was found within a form"

  assert_recognizes({ :controller => 'locations',
                      :action => 'new' },
                      :path => 'locations/new',
                      :method => :get)
end
```

This is a pretty comprehensive test.

* It does a GET to `locations#new` first and asserts that the correct instance
  variables were setup by the action
* It then verifies the response was a success (200)
* It then verifies that the `new` template was rendered
* It then moves on to some view testing

Now when it comes to view testing I'm going to take the approach of just making
sure the important stuff is in the view and not test for a
character-by-character exact match.  When it comes to a `new` action the most
important thing is going to be a <abbr title="HyperText Markup
Language">HTML</abbr> form element.

Let's take a look at each of those `assert_select` calls in detail.

```ruby
assert_select 'form[action=?]', locations_path, true,
  'no form to locations#create was found'
```

That one says there has to be at least 1 form whos action attribute is equal to
the url generated from the named route `#locations_path`.  The third boolean
parameter is required in order to output a nice helpful error message, a `true`
value means at least one element has to be found.  The error message is key when
it comes to `assert_select` because its default one is pretty cryptic (try it
out yourself).

```ruby
assert_select 'form input', 2,
  '2 input fields were not found within a form' do
  assert_select '[type=text][name=?]', 'location[name]', true,
    "no text field for a location's name was found within a form"
  assert_select '[type=submit][value=Create]', true,
    'no submit button was found within a form'
end
```

The next `#assert_select` says there has to be at least 1 form on the page that
has 2 input fields.  If that is satisfied it then yields each of those `input`
elements to its given block.  The 2 `#assert_select` calls inside the given
block are relative to the yielded `input` element.  The first one says one of
these `input` elements I get has to have a type attribute of text and a name
attribute of `location[name]`.  The second one says one of these `input`
elements I get has to have a type attribute of submit and a value attribute of
'Create'.

Error messages are used for all 3 assertions to improve debugging.

```ruby
assert_select 'form textarea[name=?]', 'location[address]', true,
  "no textarea for a location's address was found within a form"
```

The final `#assert_select` call says there has to be at least 1 textarea element
whos name is `location[address]` within a form element.

This `#assert_select` could not be in previous `#assert_select`'s block because
a text area in <abbr title="HyperText Markup Language">HTML</abbr> is a separate
element and NOT an 'input' element with a type attribute value of 'textarea'.

After all the `#assert_select` call an `#assert_recognizes` is used to verify
routing to `LocationsController#new`.

```ruby
assert_recognizes({ :controller => 'locations',
                    :action => 'new' },
                    :pat h => 'locations/new', :method => :get)
```

And here's the normal looking controller.

```ruby
class LocationsController < ApplicationController

  def new
    @location = Location.new
  end

end
```

And the view

In `app/views/locations/new.rhtml`:

```erb
<% form_for :location, :url => locations_path do |form| -%>
  <%= form.text_field :name %>
  <%= form.text_area :address %>
  <%= submit_tag 'Create' %>
<% end -%>
```

Now looking at my functional test method, I have 6 lines related to view
testing.  You can probably argue that a form in a view is part of the functional
requirements of the application because its absolutely needed by an action in
some controller and therefore should be tested.   Now this view is going to be
ultimately rendered in a layout that probably includes a header, navigation
menu, footer, etc. but I'm not going to test that in this functional test.

I have played around a little with ZenTest's `Test::Rails::ViewTestCase` but it
took a while to setup, required a couple of gems, had too many naming
conventions and was poorly documented when it comes to getting your feet wet
with it.  So for the time being I'm going to stick with what comes with Rails.
