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.
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:
_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:
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.
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:
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.