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
<phlip2005-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
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
rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org
To unsubscribe from this group, send email to
rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org
For more options, visit this group at
http://groups.google.com/group/rubyonrails-talk?hl=en
-~----------~----~----~----~------~----~------~--~---