Drupal 6: Posting to AJAX callbacks in SimpleTest

Chris's picture
Comments (4)
Post a new comment
Chris
30 June, 2010 - 12:20

In Drupal 6's excellent SimpleTest module, a method called drupalPost() allows you to simulate a button press on a form by taking the form's data and using HTTP POST to submit it. But what if you want to POST data to an AJAX callback URL? By default, SimpleTest checks which submit button you have pressed, but of course, when POSTing using AJAX, you probably won't have pressed a button!

Suppose you have a table of email addresses, as illustrated below, and you want your users to be able to remove them. It would be quite inconvenient to make the user check a box, press 'delete' and then wait for the page to refresh. Instead, you can provide an AJAX callback URL that will allow the email address to be removed when the 'remove' button is pressed, without needing to reload the page, making it much easier for your users.

Adding users by email address using AJAX

This generally works by having a URL, say /mymodule/delete-email, and having your JavaScript (jQuery in Drupal's case) take care of making a POST request to that URL, the POSTVARS containing perhaps the email address that is to be removed.

Now suppose this is all set up, but we want to write some tests for this functionality. There is no actual form on the page, so SimpleTest quickly runs into issues. We want to use SimpleTest's drupalPost() to make the POST request, but, as mentioned in the documentation, we need to pass a 'submit' parameter that tells SimpleTest to make sure that the appropriate submit form element is on the page, and make sure it has been pressed.

There is no way of having SimpleTest 'ignore' this submit parameter. It absolutely must be provided, and the test will fail unless that particular submit button is found on the form page.

The solution

I dug around for solutions to this problem, and asked in the Drupal IRC channels for help, but nothing was forthcoming, so I decided that the best way forward was to write my own function. In my module, I have written a 'base' class that inherits directly from DrupalWebTestCase, and then all my test classes inherit from this base.

I simply added this method to my base class, thereby making it available to all test classes:

  1. /**
  2.  * Simplified version of drupalPost() that allows posting to AJAX.
  3.  *
  4.  * This version of the function removes the check on the submit button, so
  5.  * it can be used to POST data to an AJAX callback URL. It does no checking
  6.  * on the result of the POST, so you will need to handle this yourself.
  7.  */
  8. protected function simplePost($path, $edit, array $options = array(), array $headers = array()) {
  9.   $action = $this->getAbsoluteUrl($path);
  10.   $post = $edit;
  11.  
  12.   foreach ($post as $key => $value) {
  13.     // Encode according to application/x-www-form-urlencoded
  14.     // Both names and values needs to be urlencoded.
  15.     $post[$key] = urlencode($key) . '=' . urlencode($value);
  16.   }
  17.   $post = implode('&', $post);
  18.  
  19.   $out = $this->curlExec(array(CURLOPT_URL => $action, CURLOPT_POST => TRUE, CURLOPT_POSTFIELDS => $post, CURLOPT_HTTPHEADER => $headers));
  20.   // Ensure that any changes to variables in the other thread are picked up.
  21.   $this->refreshVariables();
  22.  
  23.   $this->pass('simplePost has posted data to ' . $path);
  24. }

This works very similarly to the way drupalPost() works, except that it does not require the 'submit' parameter to be supplied. Of course, it's a very stripped-down version, without the possibility of uploading files and without some of the more stringent checking done by drupalPost(), but it does work, and allows testing of AJAX callback URLs inside your Drupal tests.

Without this method, I would have resorted to testing only the actual functions in my module, more akin to a unit test, which would not check that the menu callbacks were correctly assigned and that all the access permissions were set up correctly. It's clearly not the ideal way to go about testing AJAX callbacks, so what method have you been using, and how would you improve on this?

4

Comments

Berdir's picture
Berdir30 June, 2010 - 15:22

Simpletest in Drupal 7 (features are usually backported, so it might be in D6 already or at least soon) has a method called drupalPostAJAX(), haven't used it myself though, but there are AJAX tests in D7.

Jeremy Zerr's picture
Jeremy Zerr26 October, 2010 - 23:09

Nice information, I was looking for the same thing, hoping it was built into Drupal 6, but couldn't find it either. Your function works well for me.

I noticed your function left out some of the verbose output that drupalPost has. I just added these lines at the end of the function, that I took right from the end of drupalPost.

$this->verbose(
'POST request to: ' . $path .
'Ending URL: ' . $this->getUrl() .
'Fields: ' . highlight_string('<?php ' . var_export($post, TRUE), TRUE) .
'' . $out);
return $out;

Geoff Hankerson's picture
Geoff Hankerson23 March, 2012 - 20:26

This is great but there is an underlying assumption I think that if you use curl in this way you have to let anonymous users have permissions to make the post or find a way to use the session with curl.

Thanks for a great solution to a tricky problem

Geoff Hankerson's picture
Geoff Hankerson23 March, 2012 - 20:57

Correct myself, Drupal's simpletest classes have its own implementation of curl with session support - so this solution is even better than I thought originally.

Just got this working for my module

Add new comment