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 <[EMAIL PROTECTED]> wrote:
>
> [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_Rails_Example_Applications.zip
>
> --
>   Phlip
>
>
> >
>



-- 
Robby Russell
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]

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups "Ruby 
on Rails: Talk" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/rubyonrails-talk?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to