PhpRiot
Become Zend Certified

Prepare for the ZCE exam using our quizzes (web or iPad/iPhone). More info...


When you're ready get 7.5% off your exam voucher using voucher CJQNOV23 at the Zend Store

Examples

Knowing how to setup your testing infrastructure and how to make assertions is only half the battle; now it's time to start looking at some actual testing scenarios to see how you can leverage them.

Example 957. Testing a UserController

Let's consider a standard task for a website: authenticating and registering users. In our example, we'll define a UserController for handling this, and have the following requirements:

  • If a user is not authenticated, they will always be redirected to the login page of the controller, regardless of the action specified.

  • The login form page will show both the login form and the registration form.

  • Providing invalid credentials should result in returning to the login form.

  • Valid credentials should result in redirecting to the user profile page.

  • The profile page should be customized to contain the user's username.

  • Authenticated users who visit the login page should be redirected to their profile page.

  • On logout, a user should be redirected to the login page.

  • With invalid data, registration should fail.

We could, and should define further tests, but these will do for now.

For our application, we will define a plugin, 'Initialize', that runs at routeStartup(). This allows us to encapsulate our bootstrap in an OOP interface, which also provides an easy way to provide a callback. Let's look at the basics of this class first:

<?php
class Bugapp_Plugin_Initialize extends Zend_Controller_Plugin_Abstract
{
    
/**
     * @var Zend_Config
     */
    
protected static $_config;

    
/**
     * @var string Current environment
     */
    
protected $_env;

    
/**
     * @var Zend_Controller_Front
     */
    
protected $_front;

    
/**
     * @var string Path to application root
     */
    
protected $_root;

    
/**
     * Constructor
     *
     * Initialize environment, root path, and configuration.
     *
     * @param  string $env
     * @param  string|null $root
     * @return void
     */
    
public function __construct($env$root null)
    {
        
$this->_setEnv($env);
        if (
null === $root) {
            
$root realpath(dirname(__FILE__) . '/../../../');
        }
        
$this->_root $root;

        
$this->initPhpConfig();

        
$this->_front Zend_Controller_Front::getInstance();
    }

    
/**
     * Route startup
     *
     * @return void
     */
    
public function routeStartup(Zend_Controller_Request_Abstract $request)
    {
        
$this->initDb();
        
$this->initHelpers();
        
$this->initView();
        
$this->initPlugins();
        
$this->initRoutes();
        
$this->initControllers();
    }

    
// definition of methods would follow...
}

This allows us to create a bootstrap callback like the following:

<?php
class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
    public function 
appBootstrap()
    {
        
$controller $this->getFrontController();
        
$controller->registerPlugin(
            new 
Bugapp_Plugin_Initialize('development')
        );
    }

    public function 
setUp()
    {
        
$this->bootstrap = array($this'appBootstrap');
        
parent::setUp();
    }

    
// ...
}

Once we have that in place, we can write our tests. However, what about those tests that require a user is logged in? The easy solution is to use our application logic to do so... and fudge a little by using the resetRequest() and resetResponse() methods, which will allow us to dispatch another request.

<?php
class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
    
// ...

    
public function loginUser($user$password)
    {
        
$this->request->setMethod('POST')
                      ->
setPost(array(
                          
'username' => $user,
                          
'password' => $password,
                      ));
        
$this->dispatch('/user/login');
        
$this->assertRedirectTo('/user/view');

        
$this->resetRequest()
             ->
resetResponse();

        
$this->request->setPost(array());

        
// ...
    
}

    
// ...
}

Now let's write tests:

<?php
class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
    
// ...

    
public function testCallWithoutActionShouldPullFromIndexAction()
    {
        
$this->dispatch('/user');
        
$this->assertController('user');
        
$this->assertAction('index');
    }

    public function 
testLoginFormShouldContainLoginAndRegistrationForms()
    {
        
$this->dispatch('/user');
        
$this->assertQueryCount('form'2);
    }

    public function 
testInvalidCredentialsShouldResultInRedisplayOfLoginForm()
    {
        
$request $this->getRequest();
        
$request->setMethod('POST')
                ->
setPost(array(
                    
'username' => 'bogus',
                    
'password' => 'reallyReallyBogus',
                ));
        
$this->dispatch('/user/login');
        
$this->assertNotRedirect();
        
$this->assertQuery('form');
    }

    public function 
testValidLoginShouldRedirectToProfilePage()
    {
        
$this->loginUser('foobar''foobar');
    }

    public function 
testAuthenticatedUserShouldHaveCustomizedProfilePage()
    {
        
$this->loginUser('foobar''foobar');
        
$this->request->setMethod('GET');
        
$this->dispatch('/user/view');
        
$this->assertNotRedirect();
        
$this->assertQueryContentContains('h2''foobar');
    }

    public function
        
testAuthenticatedUsersShouldBeRedirectedToProfileWhenVisitingLogin()
    {
        
$this->loginUser('foobar''foobar');
        
$this->request->setMethod('GET');
        
$this->dispatch('/user');
        
$this->assertRedirectTo('/user/view');
    }

    public function 
testUserShouldRedirectToLoginPageOnLogout()
    {
        
$this->loginUser('foobar''foobar');
        
$this->request->setMethod('GET');
        
$this->dispatch('/user/logout');
        
$this->assertRedirectTo('/user');
    }

    public function 
testRegistrationShouldFailWithInvalidData()
    {
        
$data = array(
            
'username' => 'This will not work',
            
'email'    => 'this is an invalid email',
            
'password' => 'Th1s!s!nv@l1d',
            
'passwordVerification' => 'wrong!',
        );
        
$request $this->getRequest();
        
$request->setMethod('POST')
                ->
setPost($data);
        
$this->dispatch('/user/register');
        
$this->assertNotRedirect();
        
$this->assertQuery('form .errors');
    }
}

Notice that these are terse, and, for the most part, don't look for actual content. Instead, they look for artifacts within the response -- response codes and headers, and DOM nodes. This allows you to verify that the structure is as expected -- preventing your tests from choking every time new content is added to the site.

Also notice that we use the structure of the document in our tests. For instance, in the final test, we look for a form that has a node with the class of "errors"; this allows us to test merely for the presence of form validation errors, and not worry about what specific errors might have been thrown.

This application may utilize a database. If so, you will probably need some scaffolding to ensure that the database is in a pristine, testable configuration at the beginning of each test. PHPUnit already provides functionality for doing so; read about it in the PHPUnit documentation. We recommend using a separate database for testing versus production, and in particular recommend using either a SQLite file or in-memory database, as both options perform very well, do not require a separate server, and can utilize most SQL syntax.


Zend Framework