1 <?xml version="1.0" encoding="UTF-8"?>
3 <sect2 id="zend.test.phpunit.examples">
4 <title>Examples</title>
7 Knowing how to setup your testing infrastructure and how to make
8 assertions is only half the battle; now it's time to start looking at
9 some actual testing scenarios to see how you can leverage them.
12 <example id="zend.test.phpunit.examples.userController">
13 <title>Testing a UserController</title>
16 Let's consider a standard task for a website: authenticating and registering users. In
17 our example, we'll define a UserController for handling this, and have the following
24 If a user is not authenticated, they will always be redirected
25 to the login page of the controller, regardless of the action
32 The login form page will show both the login form and the
39 Providing invalid credentials should result in returning to the login form.
45 Valid credentials should result in redirecting to the user profile page.
51 The profile page should be customized to contain the user's username.
57 Authenticated users who visit the login page should be
58 redirected to their profile page.
64 On logout, a user should be redirected to the login page.
70 With invalid data, registration should fail.
76 We could, and should define further tests, but these will do for
81 For our application, we will define a plugin, 'Initialize', that
82 runs at <methodname>routeStartup()</methodname>. This allows us to encapsulate
83 our bootstrap in an OOP interface, which also provides an easy way
84 to provide a callback. Let's look at the basics of this class
88 <programlisting language="php"><![CDATA[
89 class Bugapp_Plugin_Initialize extends Zend_Controller_Plugin_Abstract
94 protected static $_config;
97 * @var string Current environment
102 * @var Zend_Controller_Front
107 * @var string Path to application root
114 * Initialize environment, root path, and configuration.
117 * @param string|null $root
120 public function __construct($env, $root = null)
122 $this->_setEnv($env);
123 if (null === $root) {
124 $root = realpath(dirname(__FILE__) . '/../../../');
126 $this->_root = $root;
128 $this->initPhpConfig();
130 $this->_front = Zend_Controller_Front::getInstance();
138 public function routeStartup(Zend_Controller_Request_Abstract $request)
141 $this->initHelpers();
143 $this->initPlugins();
145 $this->initControllers();
148 // definition of methods would follow...
153 This allows us to create a bootstrap callback like the following:
156 <programlisting language="php"><![CDATA[
157 class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
159 public function appBootstrap()
161 $controller = $this->getFrontController();
162 $controller->registerPlugin(
163 new Bugapp_Plugin_Initialize('development')
167 public function setUp()
169 $this->bootstrap = array($this, 'appBootstrap');
178 Once we have that in place, we can write our tests. However, what
179 about those tests that require a user is logged in? The easy
180 solution is to use our application logic to do so... and fudge a
181 little by using the <methodname>resetRequest()</methodname> and
182 <methodname>resetResponse()</methodname> methods, which will allow us to
183 dispatch another request.
186 <programlisting language="php"><![CDATA[
187 class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
191 public function loginUser($user, $password)
193 $this->request->setMethod('POST')
196 'password' => $password,
198 $this->dispatch('/user/login');
199 $this->assertRedirectTo('/user/view');
201 $this->resetRequest()
204 $this->request->setPost(array());
214 Now let's write tests:
217 <programlisting language="php"><![CDATA[
218 class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
222 public function testCallWithoutActionShouldPullFromIndexAction()
224 $this->dispatch('/user');
225 $this->assertController('user');
226 $this->assertAction('index');
229 public function testLoginFormShouldContainLoginAndRegistrationForms()
231 $this->dispatch('/user');
232 $this->assertQueryCount('form', 2);
235 public function testInvalidCredentialsShouldResultInRedisplayOfLoginForm()
237 $request = $this->getRequest();
238 $request->setMethod('POST')
240 'username' => 'bogus',
241 'password' => 'reallyReallyBogus',
243 $this->dispatch('/user/login');
244 $this->assertNotRedirect();
245 $this->assertQuery('form');
248 public function testValidLoginShouldRedirectToProfilePage()
250 $this->loginUser('foobar', 'foobar');
253 public function testAuthenticatedUserShouldHaveCustomizedProfilePage()
255 $this->loginUser('foobar', 'foobar');
256 $this->request->setMethod('GET');
257 $this->dispatch('/user/view');
258 $this->assertNotRedirect();
259 $this->assertQueryContentContains('h2', 'foobar');
263 testAuthenticatedUsersShouldBeRedirectedToProfileWhenVisitingLogin()
265 $this->loginUser('foobar', 'foobar');
266 $this->request->setMethod('GET');
267 $this->dispatch('/user');
268 $this->assertRedirectTo('/user/view');
271 public function testUserShouldRedirectToLoginPageOnLogout()
273 $this->loginUser('foobar', 'foobar');
274 $this->request->setMethod('GET');
275 $this->dispatch('/user/logout');
276 $this->assertRedirectTo('/user');
279 public function testRegistrationShouldFailWithInvalidData()
282 'username' => 'This will not work',
283 'email' => 'this is an invalid email',
284 'password' => 'Th1s!s!nv@l1d',
285 'passwordVerification' => 'wrong!',
287 $request = $this->getRequest();
288 $request->setMethod('POST')
290 $this->dispatch('/user/register');
291 $this->assertNotRedirect();
292 $this->assertQuery('form .errors');
298 Notice that these are terse, and, for the most part, don't look for
299 actual content. Instead, they look for artifacts within the
300 response -- response codes and headers, and DOM nodes. This allows
301 you to verify that the structure is as expected -- preventing your
302 tests from choking every time new content is added to the site.
306 Also notice that we use the structure of the document in our tests.
307 For instance, in the final test, we look for a form that has a node
308 with the class of "errors"; this allows us to test merely for the
309 presence of form validation errors, and not worry about what
310 specific errors might have been thrown.
314 This application <emphasis>may</emphasis> utilize a database. If
315 so, you will probably need some scaffolding to ensure that the
316 database is in a pristine, testable configuration at the beginning
317 of each test. PHPUnit already provides functionality for doing so;
319 url="http://www.phpunit.de/pocket_guide/3.3/en/database.html">read
320 about it in the PHPUnit documentation</ulink>. We recommend
321 using a separate database for testing versus production, and in
322 particular recommend using either a SQLite file or in-memory
323 database, as both options perform very well, do not require a
324 separate server, and can utilize most <acronym>SQL</acronym> syntax.