Tuesday, July 3, 2012

Using (Test::?)WWW::Mechanize to test AJAX calls

Test::WWW::Mechanize + AJAX = Love?

As we use more and more client side driven forms, testing in a standard way for unit/expanded unit tests becomes more and more difficult.

I've been in the process of creating more use case tests so as underlying components in our system changes those non-web people can verify that their changes don't break the webapp. 

This is often our REST API but can apply to DB changes or modules updates.

I've found this basic code block to work pretty well at using Test::WWW::Mechanize to simulate the situations where there are elements that use client side forms. (AJAX! But really AJAJ since we use JSON instead of XML).


BEGIN {
  use Test::More;
  use Test::Exception;
  use Test::WWW::Mechanize;
  use JSON;
}

#
# setup test data or load it from db
#

# 
# in this test, i login w/ many page, jump to account page
# then run ajax form and check json results
#
{
   diag "Running login and create user test";
   my $mech = Test::WWW::Mechanize->new;
   
   # base is defined above :)
   $mech->get_ok(sprintf('%s%s', $base, '/user/login'));

   # login
   $mech->submit_form_ok({
        #might be best to add name to enroll form
        form_number => 1,
        fields      => {
            email     => $test_email,
            password  => $test_password,
        }
    }, '/user/login');

    # goto next page
    $mech->get_ok( sprintf( '%s%s', $base, '/users' ));
    
    # in my case the form doesn't exist on the page until js 
    # code is run so we have to pretend to know it
 
    # pretend to be ajax client
    $mech->add_header( 'X-Requested-With' => 'XMLHttpRequest' );

    # form url
    my $create_url = sprintf '%s%s', $base, '/user/create';

    #
    # this is a failure case
    #
    my $resp  = $mech->post( $create_url, $bad_user_params );

    lives_ok { $jresp = from_json( $mech->content ) }
      'invalid user - json returned';
    
    # check response for errors and specific fields, YMMV
    is scalar @{$jresp->{errors}}, 5, 'missing fields';
    ok grep { qr/confirm_email/ } @{ $jresp->{errors} },
       'errors - confirm_email';
}

Its a bit of a long example but the real magic happens with adding the header to the request with $mech->add_header line, you will probably need to go back to standard requests by removing that header with $mech->delete_header('X-Requested-With').

Next series of my tests will be doing this with forms that exist in the page. I suspect that it may take a bit of fiddle with WWW::Mech.

 __END__

5 comments:

  1. Hi Lee! Do you guys use Selenium? We are writing Perl tests using Selenium which starts up a browser and interacts with your site using javascript. See Test::WWW::Selenium and maybe Test::WWW::Selenium::More (which I wrote). There would be some initial yak shaving to set up automated testing, etc. But the advantage (over WWW::Mechanize) is that it exercises your javascript.

    ReplyDelete
  2. Hi Eric,

    Great to hear from you. We do a little bit of testing with Selenium. I'm working on adding more but its slow going. I'll take a look at the Test::WWW::Selenium, i'd love to find a way to integrate those with our Jenkins test suite.

    When uses Test::WWW::Selenium (and Test::WWW::Selenium::More) do you first record you tests with the IDE or do you hand code the JS?

    ReplyDelete
  3. The tests are hand coded in Perl. Coincidentally I did a presentation last night to London.pm about Selenium testing with Perl. You can view the slides here (there is some example code):

    http://kablamo.org/selenium-2012-londonpm-slides/

    We use Jenkins as well. Feel free to email me with questions.

    ReplyDelete
  4. Great.. Tutorial is just awesome..It is really helpful for a newbie like me...

    JavaScript Online Training JavaScript Online Training JQuery Online Training JQuery Online Training
    JavaScript Course | HTML5 Online Training

    ReplyDelete