Saturday, July 04, 2009

Zend_Test and captchas

Do you want test automation for a Zend Framework application that contains captchas?
Zend_Test is a useful component that permits stubbing of http request parameters, methods and headers to allow integration testing of an application. It works by dispatching urls and requests and asserting that the response contains the necessary data and http headers.
This testing automation tool shows problems when encounter a captcha field. Since it is built to prevent doing automatic submission of forms, not being capable of automate the test is a good sign that the captcha generation code is well written.
However, no matter which captcha adapter a form is using, there's a workaround that permits a test to access the captcha value while hidden it from an end user. This trick access $_SESSION variable.

Architecture of Zend_Captcha
A Zend_Form_Element_Captcha uses a Zend_Captcha_Adapter_* instance, that when generating a couple of (id,input) saves the input in a namespace of the session superglobal and sends to the element for rendering only id; an empty text input is added by the element.
So the workflow is viewing a form, extracting the captcha id, pull up from $_SESSION the right input value and submit the form like we were human capable of deciphering it - err, we are human, but phpunit's not.

Here's the code
Add this method in your test case class, or wherever you want, since it has no dependencies. Only requirement is that it must be callable from a Zend_Test_PHPUnit_ControllerTestCase instance. The argument is the html of the page containing the form.
public function getCaptcha($html)
{
$dom = new Zend_Dom_Query($html);
$id = $dom->query('#captcha-id')->current()->getAttribute('value');

foreach ($_SESSION as $key => $value) {
if (ereg("Zend_Form_Captcha_(.*)", $key, $regs)) {
if ($regs[1] == $id) {
return array(
'id' => $id,
'input' => $value['word']
);
}
}
}
}

Typical usage. In this example I assume the element is named 'captcha'.
// viewing form for adding comment via ajax
$this->dispatch("/content/article/{$articleSlug}/add?format=html");
$this->assertQuery('form#otk_content_form_comment');

// adding comment
$html = $this->response->getBody();
$this->newRequest();
$this->request
->setMethod('POST')
->setPost(array(
'author' => uniqid(),
'mail' => 'integration_mail@example.com',
'text' => $text,
'suscription' => true,
'captcha' => $captcha = $this->getCaptcha($html)
));
$this->dispatch("/content/article/{$articleSlug}/add");
$this->assertRedirectTo("/content/article/{$articleSlug}/comments");

And now you can cover with integration testing also form with captchas.
Hope you like it! You know, they say if it ain't tested, it's broken...

1 comment:

Maxence said...
This comment has been removed by the author.

ShareThis