Jonathan Stott wrote:

> Why not just test the responses of whichever action the AJAX hits and check 
> that that content is accurate? 

We also do that.

 > That is as much as your AJAX requests care about, because they'll never see 
anything more.

Suppose method foo() calls method bar(). A root principle of developer testing 
is low-level tests on foo() are not enough. Also test bar(), in isolation from 
foo(). This helps decouple bar().

Ideally, you can then TDD from bottom-up, instead of top-down. You can add a 
new 
feature to foo() by testing and coding bar() first, then testing foo() and 
immediately passing the test. This improves flexibility. Tests on bar() can 
cover details foo() does not care about.

Here's what I mean, from live Rails code at work:

   def test_render_skim_panel_cc
     site_list = sites(:doctors_site, :lawyers_site)

     render :partial => 'skim_panel',
             :locals => { :@sites => site_list,
                          :@payment_method => 'cc' }

     assert_xpath :select, :cc_skim_selection do
       site.skims.visible('cc').each do |skim|
         option = assert_xpath(:option, :value => skim.id)
         assert{ option.text == skim.name }
       end
       site.skims.visible('ach').each do |skim|
         deny_xpath :"option[ @value = '#{skim.id}' ]"
       end
     end
   end

(Parenthetically, can Merb's fixtures return a list of records from one sites() 
call like that?)

The test renders the partial that displays a panel containing a <select> list 
of 
skims (aka "special offers"). The panel contains only Credit Card offers, no 
ACH 
(Checking) offers. The first assert_xpath() call discovers the <select> list 
with the correct name. The subsequent assertions must pass inside this list - 
another option in another panel cannot cause a false positive.

The internal assert_xpath() finds each CC skim in our model, and matches its 
name. I could have written assert_xpath("option[ @id = '#{skim.id}' and @value 
= 
'#{skim.name}' ]"), but running this XPath as a Ruby DSL saves a lot of ugly 
string-mashing code.

Further, I can't tell from the have_xpath() documentation if you can nest the 
XPaths, or if you can use the returned node, like have_xpath().text. I am aware 
of XPath functions such as contains(), but it seems that more complex 
sub-assertions, in the Ruby code, are impossible.

The last assertion denies that the wrong skims appeared in the list. There are 
also lower-level tests on the model things that the partial calls, and there 
are 
higher level tests on the entire page containing this partial. Any port in a 
storm!

This test covers one of the Ajax handlers. It returns JavaScript that pushes 
the 
'skim_panel' partial into the correct container:

   def test_populate_empty_skim_list
     site_list = sites(:doctors_site, :lawyers_site)
     site_list.map(&:skims).map(&:destroy_all)
     xhr :post, :xhr_populate_skims, :sites => site_list

     assert_js_replace_html :skim_panel_cc do
       assert_xpath :select, :cc_skim_selection do
         deny_xpath '*'
       end
     end
   end

assert_js_replace_html() lexes the returned JavaScript, and finds the 
Replace.element('skim_panel_cc') call. Then it finds the string payload inside 
that, evaluates this as XHTML, and preps that to work with assert_xpath().

The assert_xpath() call detects the <select id='cc_skim_selection'>, and the 
nested deny_xpath() simply determines that it is empty.

The point of all this lexing and nesting is to pinpoint the regions that need 
test, and exclude the ones that don't. If that deny_xpath failed, its 
diagnostic 
would not report the entire page. It would only report the contents of the 
<select> tag around the assertion.

assert_js_replace_html() works a lot like the ARTS assert_rjs() call, but that 
only uses JavaScript. I told my colleague to use the latter this week, and he 
immediately discovered that a call like assert_rjs :replace_html, 
'skim_panel_cc', /\<select id=.ach_skim_selection/ could get fooled by an 
'ach_skim_selection' from the next Replace.element() call! Our Ajax is 
delightfully complex, so we don't need this kind of noise at test time.

Testing like this helps us make Ajax abuse competitive with a desktop 
environment, such as forms developed with C#. This is why I look askance at 
posts saying we should not try to get the benefits we are already getting.

-- 
   Phlip


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"merb" 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/merb?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to