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
-~----------~----~----~----~------~----~------~--~---