[MANUAL] English:
[zend.git] / documentation / manual / en / module_specs / Zend_Test-PHPUnit-Examples.xml
blob9aa66bb88b0ea3eb9724502681171bd525f18487
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!-- Reviewed: no -->
3 <sect2 id="zend.test.phpunit.examples">
4     <title>Examples</title>
6     <para>
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.
10     </para>
12     <example id="zend.test.phpunit.examples.userController">
13         <title>Testing a UserController</title>
15         <para>
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
18             requirements:
19         </para>
21         <itemizedlist>
22             <listitem>
23                 <para>
24                     If a user is not authenticated, they will always be redirected
25                     to the login page of the controller, regardless of the action
26                     specified.
27                 </para>
28             </listitem>
30             <listitem>
31                 <para>
32                     The login form page will show both the login form and the
33                     registration form.
34                 </para>
35             </listitem>
37             <listitem>
38                 <para>
39                     Providing invalid credentials should result in returning to the login form.
40                 </para>
41             </listitem>
43             <listitem>
44                 <para>
45                     Valid credentials should result in redirecting to the user profile page.
46                 </para>
47             </listitem>
49             <listitem>
50                 <para>
51                     The profile page should be customized to contain the user's username.
52                 </para>
53             </listitem>
55             <listitem>
56                 <para>
57                     Authenticated users who visit the login page should be
58                     redirected to their profile page.
59                 </para>
60             </listitem>
62             <listitem>
63                 <para>
64                     On logout, a user should be redirected to the login page.
65                 </para>
66             </listitem>
68             <listitem>
69                 <para>
70                     With invalid data, registration should fail.
71                 </para>
72             </listitem>
73         </itemizedlist>
75         <para>
76             We could, and should define further tests, but these will do for
77             now.
78         </para>
80         <para>
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
85             first:
86         </para>
88         <programlisting language="php"><![CDATA[
89 class Bugapp_Plugin_Initialize extends Zend_Controller_Plugin_Abstract
91     /**
92      * @var Zend_Config
93      */
94     protected static $_config;
96     /**
97      * @var string Current environment
98      */
99     protected $_env;
101     /**
102      * @var Zend_Controller_Front
103      */
104     protected $_front;
106     /**
107      * @var string Path to application root
108      */
109     protected $_root;
111     /**
112      * Constructor
113      *
114      * Initialize environment, root path, and configuration.
115      *
116      * @param  string $env
117      * @param  string|null $root
118      * @return void
119      */
120     public function __construct($env, $root = null)
121     {
122         $this->_setEnv($env);
123         if (null === $root) {
124             $root = realpath(dirname(__FILE__) . '/../../../');
125         }
126         $this->_root = $root;
128         $this->initPhpConfig();
130         $this->_front = Zend_Controller_Front::getInstance();
131     }
133     /**
134      * Route startup
135      *
136      * @return void
137      */
138     public function routeStartup(Zend_Controller_Request_Abstract $request)
139     {
140         $this->initDb();
141         $this->initHelpers();
142         $this->initView();
143         $this->initPlugins();
144         $this->initRoutes();
145         $this->initControllers();
146     }
148     // definition of methods would follow...
150 ]]></programlisting>
152         <para>
153             This allows us to create a bootstrap callback like the following:
154         </para>
156         <programlisting language="php"><![CDATA[
157 class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
159     public function appBootstrap()
160     {
161         $controller = $this->getFrontController();
162         $controller->registerPlugin(
163             new Bugapp_Plugin_Initialize('development')
164         );
165     }
167     public function setUp()
168     {
169         $this->bootstrap = array($this, 'appBootstrap');
170         parent::setUp();
171     }
173     // ...
175 ]]></programlisting>
177         <para>
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.
184         </para>
186         <programlisting language="php"><![CDATA[
187 class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
189     // ...
191     public function loginUser($user, $password)
192     {
193         $this->request->setMethod('POST')
194                       ->setPost(array(
195                           'username' => $user,
196                           'password' => $password,
197                       ));
198         $this->dispatch('/user/login');
199         $this->assertRedirectTo('/user/view');
201         $this->resetRequest()
202              ->resetResponse();
204         $this->request->setPost(array());
206         // ...
207     }
209     // ...
211 ]]></programlisting>
213         <para>
214             Now let's write tests:
215         </para>
217         <programlisting language="php"><![CDATA[
218 class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
220     // ...
222     public function testCallWithoutActionShouldPullFromIndexAction()
223     {
224         $this->dispatch('/user');
225         $this->assertController('user');
226         $this->assertAction('index');
227     }
229     public function testLoginFormShouldContainLoginAndRegistrationForms()
230     {
231         $this->dispatch('/user');
232         $this->assertQueryCount('form', 2);
233     }
235     public function testInvalidCredentialsShouldResultInRedisplayOfLoginForm()
236     {
237         $request = $this->getRequest();
238         $request->setMethod('POST')
239                 ->setPost(array(
240                     'username' => 'bogus',
241                     'password' => 'reallyReallyBogus',
242                 ));
243         $this->dispatch('/user/login');
244         $this->assertNotRedirect();
245         $this->assertQuery('form');
246     }
248     public function testValidLoginShouldRedirectToProfilePage()
249     {
250         $this->loginUser('foobar', 'foobar');
251     }
253     public function testAuthenticatedUserShouldHaveCustomizedProfilePage()
254     {
255         $this->loginUser('foobar', 'foobar');
256         $this->request->setMethod('GET');
257         $this->dispatch('/user/view');
258         $this->assertNotRedirect();
259         $this->assertQueryContentContains('h2', 'foobar');
260     }
262     public function
263         testAuthenticatedUsersShouldBeRedirectedToProfileWhenVisitingLogin()
264     {
265         $this->loginUser('foobar', 'foobar');
266         $this->request->setMethod('GET');
267         $this->dispatch('/user');
268         $this->assertRedirectTo('/user/view');
269     }
271     public function testUserShouldRedirectToLoginPageOnLogout()
272     {
273         $this->loginUser('foobar', 'foobar');
274         $this->request->setMethod('GET');
275         $this->dispatch('/user/logout');
276         $this->assertRedirectTo('/user');
277     }
279     public function testRegistrationShouldFailWithInvalidData()
280     {
281         $data = array(
282             'username' => 'This will not work',
283             'email'    => 'this is an invalid email',
284             'password' => 'Th1s!s!nv@l1d',
285             'passwordVerification' => 'wrong!',
286         );
287         $request = $this->getRequest();
288         $request->setMethod('POST')
289                 ->setPost($data);
290         $this->dispatch('/user/register');
291         $this->assertNotRedirect();
292         $this->assertQuery('form .errors');
293     }
295 ]]></programlisting>
297         <para>
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.
303         </para>
305         <para>
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.
311         </para>
313         <para>
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;
318             <ulink
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.
325         </para>
326     </example>
327 </sect2>
328 <!--
329 vim:se ts=4 sw=4 et: