| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330 |
- <?xml version="1.0" encoding="UTF-8"?>
- <!-- Reviewed: no -->
- <sect2 id="zend.test.phpunit.examples">
- <title>Examples</title>
- <para>
- 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.
- </para>
- <example id="zend.test.phpunit.examples.userController">
- <title>Testing a UserController</title>
- <para>
- 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:
- </para>
- <itemizedlist>
- <listitem>
- <para>
- If a user is not authenticated, they will always be redirected
- to the login page of the controller, regardless of the action
- specified.
- </para>
- </listitem>
- <listitem>
- <para>
- The login form page will show both the login form and the
- registration form.
- </para>
- </listitem>
- <listitem>
- <para>
- Providing invalid credentials should result in returning to the login form.
- </para>
- </listitem>
- <listitem>
- <para>
- Valid credentials should result in redirecting to the user profile page.
- </para>
- </listitem>
- <listitem>
- <para>
- The profile page should be customized to contain the user's username.
- </para>
- </listitem>
- <listitem>
- <para>
- Authenticated users who visit the login page should be
- redirected to their profile page.
- </para>
- </listitem>
- <listitem>
- <para>
- On logout, a user should be redirected to the login page.
- </para>
- </listitem>
- <listitem>
- <para>
- With invalid data, registration should fail.
- </para>
- </listitem>
- </itemizedlist>
- <para>
- We could, and should define further tests, but these will do for
- now.
- </para>
- <para>
- For our application, we will define a plugin, 'Initialize', that
- runs at <methodname>routeStartup()</methodname>. 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:
- </para>
- <programlisting language="php"><![CDATA[
- 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...
- }
- ]]></programlisting>
- <para>
- This allows us to create a bootstrap callback like the following:
- </para>
- <programlisting language="php"><![CDATA[
- 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();
- }
- // ...
- }
- ]]></programlisting>
- <para>
- 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 <methodname>resetRequest()</methodname> and
- <methodname>resetResponse()</methodname> methods, which will allow us to
- dispatch another request.
- </para>
- <programlisting language="php"><![CDATA[
- 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());
- // ...
- }
- // ...
- }
- ]]></programlisting>
- <para>
- Now let's write tests:
- </para>
- <programlisting language="php"><![CDATA[
- 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');
- }
- }
- ]]></programlisting>
- <para>
- 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.
- </para>
- <para>
- 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.
- </para>
- <para>
- This application <emphasis>may</emphasis> 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;
- <ulink
- url="http://www.phpunit.de/manual/3.4/en/database.html">read
- about it in the PHPUnit documentation</ulink>. 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 <acronym>SQL</acronym> syntax.
- </para>
- </example>
- </sect2>
- <!--
- vim:se ts=4 sw=4 et:
- -->
|