Kevin
Kevin

Reputation: 958

Unit testing forms that have a hash element in Zend Framework

I'm trying to test valid form data in one of my Zend_Forms however it is failing due to it having a hash element that is generated randomly and I cannot access the generated hash to put it back into the assertion data. E.g.

$form = new MyForm();
$data = array('username'=>'test');
$this->assertTrue($form->isValid($data));

This fails as it doesn't contain the hash element value.

Upvotes: 3

Views: 1548

Answers (3)

t j
t j

Reputation: 7294

Thanks singles. Rendering the form before the tests worked perfectly for my problem. I wouldn't be too happy about removing the hash element for testing as:

  1. You'll be adding a certain amount of pointless code to remove these elements during testing.
  2. Security features need to be tested too.

Here's a quick example of how I did it:

public function testLoginSetsSession()
{
    // must render the form first to generate the CSRF hash
    $form = new Form_Login();
    $form->render();

    $this->request
         ->setMethod('POST')
         ->setPost(array(
             'email' => '[email protected]',
             'password' => 'password',
             'hash' => $form->hash->getValue()
         ));
    $this->dispatch('/login');

    $this->assertTrue(Zend_Auth::getInstance()->hasIdentity());
}

Upvotes: 3

Jerry Saravia
Jerry Saravia

Reputation: 3837

I recently found a great way of testing forms with hash elements. This will use a mock object to stub away the hash element and you won't have to worry about it. You won't even have to do a session_start or anything this way. You won't have to 'prerender' the form either.

First create a 'stub' class like so



    class My_Form_Element_HashStub extends Zend_Form_Element_Hash
    {
        public function __construct(){}
    }


Then, add the following to the form somewhere.


    class MyForm extends Zend_Form{

    protected $_hashElement;

    public function setHashElement( Zend_Form_Hash_Element $hash )
    { 
      $this->_hashElement = $hash; 
      return $this; 
    }

    protected function _getHashElement( $name = 'hashElement' )
    { 
      if( !isset( $this->_hashElement )
      {
          if( isset( $name ) )
          {
                $element = new Zend_Form_Element_Hash( $name, 
                                                      array( 'id' => $name ) );
          }
          else
          {
                $element = new Zend_Form_Element_Hash( 'hashElement', 
                                            array( 'id' => 'hashElement' ) );
          }

         $this->setHashElement( $element );
         return $this->_hashElement;
      }
    }
    /**
     *
     * In your init method you can now add the hash element like below
     *
     *
     *
     */

    public function init()
    {
        //other code
        $this->addElement( $this->_getHashElement( 'myotherhashelementname' );
        //other code
    }

The set method is there just for testing purposes really. You probably won't use it at all during real use but now in phpunit you can right the following.

class My_Form_LoginTest
        extends PHPUnit_Framework_TestCase
{

    /**
     *
     * @var My_Form_Login
     */
    protected $_form;
    /**
     *
     * @var PHPUnit_Framework_MockObject_MockObject
     */
    protected $_hash;

    public function setUp()
    {
        parent::setUp();
        $this->_hash = $this->getMock( 'My_Form_Element_HashStub' );

        $this->_form = new My_Form_Login( array(
                    'action'                    => '/',
                    'hashElement'               => $this->_hash
    }

    public function testTrue()
    {   
        //The hash element will now always validate to true
        $this->_hash
             ->expects( $this->any() )
             ->method( 'isValid' )
             ->will( $this->returnValue( true ) );

        //OR if you need it to validate to false
                $this->_hash
             ->expects( $this->any() )
             ->method( 'isValid' )
             ->will( $this->returnValue( true ) );

    }

You HAVE to create your own stub. You can't just call the phpunit method getMockObject because that will directly extend the hash element and the normal hash element does 'evil' stuff in its constructor.

With this method you don't even need to be connected to a database to test your forms! It took me a while to think of this.

If you want, you can push the setHashElement method ( along with the variable and the get method ) into some FormAbstract base class.

REMEMBER, in phpunit you HAVE to pass the hash element during form construction. If you don't, your init method will get called before your stub hash can be set with the set method and you'll end up using the regular hash element. You'll know you're using the regular hash element because you'll probably get some session error if you're NOT connected to a database.

Let me know if you find this helpful or if you use it.

Upvotes: 1

Radek
Radek

Reputation: 8376

I had same problem when my form had captcha and I wanted to test it. Two possible solutions that I cant think about:

  1. First render the form (hash will be generated then), then take that element, take value and use it to test form.
  2. Just remove hash element for testing.

Upvotes: 4

Related Questions