Zend_Test-PHPUnit-Examples.xml 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!-- Reviewed: no -->
  3. <sect2 id="zend.test.phpunit.examples">
  4. <title>Examples</title>
  5. <para>
  6. Knowing how to setup your testing infrastructure and how to make
  7. assertions is only half the battle; now it's time to start looking at
  8. some actual testing scenarios to see how you can leverage them.
  9. </para>
  10. <example id="zend.test.phpunit.examples.userController">
  11. <title>Testing a UserController</title>
  12. <para>
  13. Let's consider a standard task for a website: authenticating and registering users. In
  14. our example, we'll define a UserController for handling this, and have the following
  15. requirements:
  16. </para>
  17. <itemizedlist>
  18. <listitem>
  19. <para>
  20. If a user is not authenticated, they will always be redirected
  21. to the login page of the controller, regardless of the action
  22. specified.
  23. </para>
  24. </listitem>
  25. <listitem>
  26. <para>
  27. The login form page will show both the login form and the
  28. registration form.
  29. </para>
  30. </listitem>
  31. <listitem>
  32. <para>
  33. Providing invalid credentials should result in returning to the login form.
  34. </para>
  35. </listitem>
  36. <listitem>
  37. <para>
  38. Valid credentials should result in redirecting to the user profile page.
  39. </para>
  40. </listitem>
  41. <listitem>
  42. <para>
  43. The profile page should be customized to contain the user's username.
  44. </para>
  45. </listitem>
  46. <listitem>
  47. <para>
  48. Authenticated users who visit the login page should be
  49. redirected to their profile page.
  50. </para>
  51. </listitem>
  52. <listitem>
  53. <para>
  54. On logout, a user should be redirected to the login page.
  55. </para>
  56. </listitem>
  57. <listitem>
  58. <para>
  59. With invalid data, registration should fail.
  60. </para>
  61. </listitem>
  62. </itemizedlist>
  63. <para>
  64. We could, and should define further tests, but these will do for
  65. now.
  66. </para>
  67. <para>
  68. For our application, we will define a plugin, 'Initialize', that
  69. runs at <methodname>routeStartup()</methodname>. This allows us to encapsulate
  70. our bootstrap in an OOP interface, which also provides an easy way
  71. to provide a callback. Let's look at the basics of this class
  72. first:
  73. </para>
  74. <programlisting language="php"><![CDATA[
  75. class Bugapp_Plugin_Initialize extends Zend_Controller_Plugin_Abstract
  76. {
  77. /**
  78. * @var Zend_Config
  79. */
  80. protected static $_config;
  81. /**
  82. * @var string Current environment
  83. */
  84. protected $_env;
  85. /**
  86. * @var Zend_Controller_Front
  87. */
  88. protected $_front;
  89. /**
  90. * @var string Path to application root
  91. */
  92. protected $_root;
  93. /**
  94. * Constructor
  95. *
  96. * Initialize environment, root path, and configuration.
  97. *
  98. * @param string $env
  99. * @param string|null $root
  100. * @return void
  101. */
  102. public function __construct($env, $root = null)
  103. {
  104. $this->_setEnv($env);
  105. if (null === $root) {
  106. $root = realpath(dirname(__FILE__) . '/../../../');
  107. }
  108. $this->_root = $root;
  109. $this->initPhpConfig();
  110. $this->_front = Zend_Controller_Front::getInstance();
  111. }
  112. /**
  113. * Route startup
  114. *
  115. * @return void
  116. */
  117. public function routeStartup(Zend_Controller_Request_Abstract $request)
  118. {
  119. $this->initDb();
  120. $this->initHelpers();
  121. $this->initView();
  122. $this->initPlugins();
  123. $this->initRoutes();
  124. $this->initControllers();
  125. }
  126. // definition of methods would follow...
  127. }
  128. ]]></programlisting>
  129. <para>
  130. This allows us to create a bootstrap callback like the following:
  131. </para>
  132. <programlisting language="php"><![CDATA[
  133. class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
  134. {
  135. public function appBootstrap()
  136. {
  137. $controller = $this->getFrontController();
  138. $controller->registerPlugin(
  139. new Bugapp_Plugin_Initialize('development')
  140. );
  141. }
  142. public function setUp()
  143. {
  144. $this->bootstrap = array($this, 'appBootstrap');
  145. parent::setUp();
  146. }
  147. // ...
  148. }
  149. ]]></programlisting>
  150. <para>
  151. Once we have that in place, we can write our tests. However, what
  152. about those tests that require a user is logged in? The easy
  153. solution is to use our application logic to do so... and fudge a
  154. little by using the <methodname>resetRequest()</methodname> and
  155. <methodname>resetResponse()</methodname> methods, which will allow us to
  156. dispatch another request.
  157. </para>
  158. <programlisting language="php"><![CDATA[
  159. class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
  160. {
  161. // ...
  162. public function loginUser($user, $password)
  163. {
  164. $this->request->setMethod('POST')
  165. ->setPost(array(
  166. 'username' => $user,
  167. 'password' => $password,
  168. ));
  169. $this->dispatch('/user/login');
  170. $this->assertRedirectTo('/user/view');
  171. $this->resetRequest()
  172. ->resetResponse();
  173. $this->request->setPost(array());
  174. // ...
  175. }
  176. // ...
  177. }
  178. ]]></programlisting>
  179. <para>
  180. Now let's write tests:
  181. </para>
  182. <programlisting language="php"><![CDATA[
  183. class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
  184. {
  185. // ...
  186. public function testCallWithoutActionShouldPullFromIndexAction()
  187. {
  188. $this->dispatch('/user');
  189. $this->assertController('user');
  190. $this->assertAction('index');
  191. }
  192. public function testLoginFormShouldContainLoginAndRegistrationForms()
  193. {
  194. $this->dispatch('/user');
  195. $this->assertQueryCount('form', 2);
  196. }
  197. public function testInvalidCredentialsShouldResultInRedisplayOfLoginForm()
  198. {
  199. $request = $this->getRequest();
  200. $request->setMethod('POST')
  201. ->setPost(array(
  202. 'username' => 'bogus',
  203. 'password' => 'reallyReallyBogus',
  204. ));
  205. $this->dispatch('/user/login');
  206. $this->assertNotRedirect();
  207. $this->assertQuery('form');
  208. }
  209. public function testValidLoginShouldRedirectToProfilePage()
  210. {
  211. $this->loginUser('foobar', 'foobar');
  212. }
  213. public function testAuthenticatedUserShouldHaveCustomizedProfilePage()
  214. {
  215. $this->loginUser('foobar', 'foobar');
  216. $this->request->setMethod('GET');
  217. $this->dispatch('/user/view');
  218. $this->assertNotRedirect();
  219. $this->assertQueryContentContains('h2', 'foobar');
  220. }
  221. public function
  222. testAuthenticatedUsersShouldBeRedirectedToProfileWhenVisitingLogin()
  223. {
  224. $this->loginUser('foobar', 'foobar');
  225. $this->request->setMethod('GET');
  226. $this->dispatch('/user');
  227. $this->assertRedirectTo('/user/view');
  228. }
  229. public function testUserShouldRedirectToLoginPageOnLogout()
  230. {
  231. $this->loginUser('foobar', 'foobar');
  232. $this->request->setMethod('GET');
  233. $this->dispatch('/user/logout');
  234. $this->assertRedirectTo('/user');
  235. }
  236. public function testRegistrationShouldFailWithInvalidData()
  237. {
  238. $data = array(
  239. 'username' => 'This will not work',
  240. 'email' => 'this is an invalid email',
  241. 'password' => 'Th1s!s!nv@l1d',
  242. 'passwordVerification' => 'wrong!',
  243. );
  244. $request = $this->getRequest();
  245. $request->setMethod('POST')
  246. ->setPost($data);
  247. $this->dispatch('/user/register');
  248. $this->assertNotRedirect();
  249. $this->assertQuery('form .errors');
  250. }
  251. }
  252. ]]></programlisting>
  253. <para>
  254. Notice that these are terse, and, for the most part, don't look for
  255. actual content. Instead, they look for artifacts within the
  256. response -- response codes and headers, and DOM nodes. This allows
  257. you to verify that the structure is as expected -- preventing your
  258. tests from choking every time new content is added to the site.
  259. </para>
  260. <para>
  261. Also notice that we use the structure of the document in our tests.
  262. For instance, in the final test, we look for a form that has a node
  263. with the class of "errors"; this allows us to test merely for the
  264. presence of form validation errors, and not worry about what
  265. specific errors might have been thrown.
  266. </para>
  267. <para>
  268. This application <emphasis>may</emphasis> utilize a database. If
  269. so, you will probably need some scaffolding to ensure that the
  270. database is in a pristine, testable configuration at the beginning
  271. of each test. PHPUnit already provides functionality for doing so;
  272. <ulink
  273. url="http://www.phpunit.de/manual/3.4/en/database.html">read
  274. about it in the PHPUnit documentation</ulink>. We recommend
  275. using a separate database for testing versus production, and in
  276. particular recommend using either a SQLite file or in-memory
  277. database, as both options perform very well, do not require a
  278. separate server, and can utilize most <acronym>SQL</acronym> syntax.
  279. </para>
  280. </example>
  281. </sect2>
  282. <!--
  283. vim:se ts=4 sw=4 et:
  284. -->