Forum: Ruby on Rails Rails Partial Tests

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Phlip (Guest)
on 2008-10-05 15:33
(Received via mailing list)
[A preview of my next blog entry]

Testing Rails Partials

    One important metric, under Test Driven Development, is the distance
    between a test case and its target code. Test cases use assertions
to
    observe events inside programs. If a test case requires more than a
few
    hops to reach its target event, the intermediate methods can add
noise
    to the test's signal.

    Some architectures make decoupling test cases very hard. This post
    develops a fix for an icky Rails problem - testing one small partial
    .rhtml file embedded in a huge web page.

   Rails View Testing

    Rails projects can test web pages by rendering them to HTML, then
    diverting them into test cases. Rails functional tests can get
    controller actions, then parse web pages, returned in
@response.body,
    to match important details.

    (If your web pages are pure XHTML [a very good idea], you can test
them
    with assert_xpath. If they are not, call assert_tidy before
    assert_xpath.)

    Anything your production code pushes into a web page, with <%= %>
eRB
    tags, a test should pull out, using assert_match, assert_xpath, or
    assert_select.

    However, such tests can be noisy. A test that detects an important
    number, such as 42 in an input field, should not trip over any
    irrelevant 42s, such as a nearby <img width='42'>. When tests run
    closer to their tested code, their signal gets stronger.

    Rails can generate HTML by pushing .rhtml files (or .html.erb files)
    together with layout files and partial files. A partial is Rails's
unit
    of HTML reuse. A Rails View can render a partial and insert it into
its
    hosting HTML like this:
<%= render :partial => "photos/show" %>

    Test Driven Development works best when each test case targets one
    aspect of a class's interface. So this post will demonstrate a
simple
    and direct way to test a partial without testing the Views, layouts,
    and Controller actions surrounding it. On very complex projects,
this
    technique keeps your partials decoupled.

    This is the Photo Gallery project from [1]Ajax on Rails, by Scott
    Raymond. I upgraded it to use Rails 2.1, yet these techniques all
work
    freely with any Ruby on Rails version >1.4. Then I added a simple
test
    to its action that shows a gallery of thumbnails:

require File.dirname(__FILE__) + '/../test_helper'
require 'albums_controller'
require 'assert_xpath'
require 'assert2'

class AlbumsController; def rescue_action(e) raise e end; end

class AlbumsControllerTest < ActionController::TestCase
   include AssertXPath
   fixtures :albums, :photos # add a couple real fixtures here first!

   def test_show
     album = albums(:first)
     get :show, :id => album.id

     assert_xpath :div, :photos, 'find <div id="photos">' do
       assert_xpath :'ul/li', album.photos.first.id,
                                 'finds <ul><li id="999">' do
         assert{ assert_xpath(:a)[:onclick] =~ /Photo.show/ }
       end
     end
   end

end

    The line with get :show, :id => album.id simulates a user hitting
the
    show action with the id of a photo album. The page comes back in the
    secret variable @response.body with the rendered HTML.

   An XPath DSL

    The first assert_xpath converts that HTML into an XML document. The
    notation :div, :photos is one of assert_xpath's Domain Specific
    Language shortcuts. It expands to the complete XPath '//div[ @id =
    "photos" ]'. You could write all that too, if you wanted.

    When assert_xpath's first argument is a 'string', it evaluates as
raw
    XPath. When it's a :symbol, assert_xpath tacks a // on the beginning
    (or the equivalent), meaning "seek any such node at or below the
    current node".

    Both forms of assert_xpath return only one node - the first one
    encountered.

    The last argument to assert_xpath is a diagnostic string. When
    assert_xpath fails, it prints out this string, decorated with the
    current HTML context.

    When you call assert_xpath with a block, it narrows that context to
    that block. Any assert_xpath calls inside that block can only match
    HTML elements inside that container.

    If the line assert_xpath :'ul/li'... failed, it would spew out only
the
    contents of <div id='photos'>. This is very important in Web
    development, because a complete HTML page could be several screens
    long. Most of it would not relate to the tested feature.

    In summary, that test case detects this HTML:
...<div id='photos'>
      <ul>
         <li id='520095529'>
            <a onclick='Photo.show...'>...</a>
          </li>
      </ul>
    </div>...

    I replaced the elements it did not detect with ... ellipses. Further
    assertions could easily pin down their contents, if they were
    important.

   Cut to the Chase

    The code which generated that HTML looks like this:

<div id="photos"><%= render :partial => "photos/index" %></div>

    That looks mostly harmless, but imagine if all the other show.rhtml
    business around it were heavy and expensive; fraught with
side-effects.
    Imagine if we needed to add an important feature into that partial,
    requiring many test cases. Each test case would have to call extra
code
    to support those side-effects. Expensive test setup is a design
smell -
    it indicates code that's too coupled. When tests run close to their
    target code, they help it decouple.

    Here's how to test that partial directly:

class ApplicationController
   def _renderizer;  render params[:args];  end
end

class ActionController::TestCase # or Test::Unit::TestCase, for Rails
<2.0
   def render(args);  get :_renderizer, :args => args;  end
end

    ...
   def test_photo_index
     album = albums(:first)

     render :partial => 'photos/index',
             :locals => { :@album => album }

     assert_xpath :'ul/li', album.photos.first.id,
                               'finds <ul><li id="999">' do
       assert{ assert_xpath(:a)[:onclick] =~ /Photo.show/ }
     end
   end

    That wasn't too horrifying now was it?

    In general, Rails's internal architecture can be labyrinthine.
That's
    the price of incredible flexibility. Writing your own copy of render
is
    hard, because a test using ActionController::TestCase does not yet
have
    an ActiveView context. The test method get must concoct one using
the
    same procedures as a real web hit.

    To bypass this problem, we first add a secret action to all
controllers
    - _renderizer. Because Ruby is extensible, the runtime application
    never sees this method. It only appears under test. We implement
render
    by packing up its arguments, passing them thru get to _renderizer,
and
    letting it call render.

    The benefit is our test case requires one less assertion. A more
    complex application could have avoided much more cruft there. And if
    our assert_xpath failed, now, its diagnostic would report only the
    partial's own contents.

    And the mock render can generate any other View-level thing,
isolating
    it for test. For example, it could test that our application layout
has
    a link called "Gallery", like this:

   def test_layout
     render :layout => 'application', :nothing => true
     assert_xpath :'a[ . = "Gallery" ]'
   end

    This lightweight solution to a tricky Rails problem illustrates how
    Ruby applications in general, and Rails in particular, reward
thinking
    outside the box.

References

    1.
http://examples.oreilly.com/9780596527440/Ajax_on_...

--
   Phlip
Robby R. (Guest)
on 2008-10-06 03:12
(Received via mailing list)
Did you have a question? I might be mistaken... but I'm not sure the
purpose of rails-talk is to submit your blog posts.

On Sun, Oct 5, 2008 at 4:32 AM, Phlip <removed_email_address@domain.invalid> 
wrote:
>
>
>    irrelevant 42s, such as a nearby <img width='42'>. When tests run
>    and direct way to test a partial without testing the Views, layouts,
> require 'assert_xpath'
>     get :show, :id => album.id
>
>
>    current HTML context.
>    In summary, that test case detects this HTML:
>    important.
>    requiring many test cases. Each test case would have to call extra code
> class ActionController::TestCase # or Test::Unit::TestCase, for Rails <2.0
>     assert_xpath :'ul/li', album.photos.first.id,
>    an ActiveView context. The test method get must concoct one using the
>    our assert_xpath failed, now, its diagnostic would report only the
>
>   Phlip
>
>
> >
>



--
Robby R.
Chief Evangelist, Partner

PLANET ARGON, LLC
design // development // hosting

http://www.planetargon.com/
http://www.robbyonrails.com/
aim: planetargon

+1 503 445 2457
+1 877 55 ARGON [toll free]
+1 815 642 4068 [fax]
Phlip (Guest)
on 2008-10-09 19:07
(Received via mailing list)
> [A preview of my next blog entry]

Aaaaand - if I can trust the new blogging system there - my next blog
entry is up:

http://broadcast.oreilly.com/2008/10/testing-rails...

--
  Phlip
This topic is locked and can not be replied to.