ControllerTestCase.php 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. <?php
  2. /**
  3. * ControllerTestCase file
  4. *
  5. * PHP 5
  6. *
  7. * CakePHP(tm) Tests <http://book.cakephp.org/view/1196/Testing>
  8. * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
  9. *
  10. * Licensed under The MIT License
  11. * Redistributions of files must retain the above copyright notice
  12. *
  13. * @copyright Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
  14. * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests
  15. * @package Cake.TestSuite
  16. * @since CakePHP(tm) v 2.0
  17. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  18. */
  19. PHP_CodeCoverage_Filter::getInstance()->addFileToBlacklist(__FILE__, 'DEFAULT');
  20. App::uses('Dispatcher', 'Routing');
  21. App::uses('CakeTestCase', 'TestSuite');
  22. App::uses('Router', 'Routing');
  23. App::uses('CakeRequest', 'Network');
  24. App::uses('CakeResponse', 'Network');
  25. App::uses('Helper', 'View');
  26. /**
  27. * ControllerTestDispatcher class
  28. *
  29. * @package Cake.TestSuite
  30. */
  31. class ControllerTestDispatcher extends Dispatcher {
  32. /**
  33. * The controller to use in the dispatch process
  34. *
  35. * @var Controller
  36. */
  37. public $testController = null;
  38. /**
  39. * Use custom routes during tests
  40. *
  41. * @var boolean
  42. */
  43. public $loadRoutes = true;
  44. /**
  45. * Returns the test controller
  46. *
  47. * @return Controller
  48. */
  49. function _getController($request, $response) {
  50. if ($this->testController === null) {
  51. $this->testController = parent::_getController($request, $response);
  52. }
  53. $this->testController->helpers = array_merge(array('InterceptContent'), $this->testController->helpers);
  54. $this->testController->setRequest($request);
  55. $this->testController->response = $this->response;
  56. foreach ($this->testController->Components->attached() as $component) {
  57. $object = $this->testController->Components->{$component};
  58. if (isset($object->response)) {
  59. $object->response = $response;
  60. }
  61. }
  62. if (isset($object->request)) {
  63. $object->request = $request;
  64. }
  65. return $this->testController;
  66. }
  67. /**
  68. * Loads routes and resets if the test case dictates it should
  69. *
  70. * @return void
  71. */
  72. protected function _loadRoutes() {
  73. parent::_loadRoutes();
  74. if (!$this->loadRoutes) {
  75. Router::reload();
  76. }
  77. }
  78. }
  79. /**
  80. * InterceptContentHelper class
  81. *
  82. * @package Cake.TestSuite
  83. */
  84. class InterceptContentHelper extends Helper {
  85. /**
  86. * Intercepts and stores the contents of the view before the layout is rendered
  87. *
  88. * @param string $viewFile The view file
  89. */
  90. public function afterRender($viewFile) {
  91. $this->_View->_viewNoLayout = $this->_View->output;
  92. $this->_View->Helpers->unload('InterceptContent');
  93. }
  94. }
  95. /**
  96. * ControllerTestCase class
  97. *
  98. * @package Cake.TestSuite
  99. */
  100. abstract class ControllerTestCase extends CakeTestCase {
  101. /**
  102. * The controller to test in testAction
  103. *
  104. * @var Controller
  105. */
  106. public $controller = null;
  107. /**
  108. * Automatically mock controllers that aren't mocked
  109. *
  110. * @var boolean
  111. */
  112. public $autoMock = false;
  113. /**
  114. * Use custom routes during tests
  115. *
  116. * @var boolean
  117. */
  118. public $loadRoutes = true;
  119. /**
  120. * The resulting view vars of the last testAction call
  121. *
  122. * @var array
  123. */
  124. public $vars = null;
  125. /**
  126. * The resulting rendered view of the last testAction call
  127. *
  128. * @var string
  129. */
  130. public $view = null;
  131. /**
  132. * The resulting rendered layout+view of the last testAction call
  133. *
  134. * @var string
  135. */
  136. public $contents = null;
  137. /**
  138. * The returned result of the dispatch (requestAction), if any
  139. *
  140. * @var string
  141. */
  142. public $result = null;
  143. /**
  144. * The headers that would have been sent by the action
  145. *
  146. * @var string
  147. */
  148. public $headers = null;
  149. /**
  150. * Flag for checking if the controller instance is dirty.
  151. * Once a test has been run on a controller it should be rebuilt
  152. * to clean up properties.
  153. *
  154. * @var boolean
  155. */
  156. private $__dirtyController = false;
  157. /**
  158. * Used to enable calling ControllerTestCase::testAction() without the testing
  159. * framework thinking that it's a test case
  160. *
  161. * @param string $name The name of the function
  162. * @param array $arguments Array of arguments
  163. * @return Function
  164. */
  165. public function __call($name, $arguments) {
  166. if ($name == 'testAction') {
  167. return call_user_func_array(array($this, '_testAction'), $arguments);
  168. }
  169. }
  170. /**
  171. * Tests a controller action.
  172. *
  173. * ### Options:
  174. *
  175. * - `data` POST or GET data to pass. Depends on the method.
  176. * - `method` POST or GET. Defaults to POST.
  177. * - `return` Specify the return type you want. Choose from:
  178. * - `vars` Get the set view variables.
  179. * - `view` Get the rendered view, without a layout.
  180. * - `contents` Get the rendered view including the layout.
  181. * - `result` Get the return value of the controller action. Useful
  182. * for testing requestAction methods.
  183. *
  184. * @param string $url The url to test
  185. * @param array $options See options
  186. */
  187. protected function _testAction($url = '', $options = array()) {
  188. $this->vars = $this->result = $this->view = $this->contents = $this->headers = null;
  189. $options = array_merge(array(
  190. 'data' => array(),
  191. 'method' => 'POST',
  192. 'return' => 'result'
  193. ), $options);
  194. $_SERVER['REQUEST_METHOD'] = strtoupper($options['method']);
  195. if (strtoupper($options['method']) == 'GET') {
  196. $_GET = $options['data'];
  197. $_POST = array();
  198. } else {
  199. $_POST = $options['data'];
  200. $_GET = array();
  201. }
  202. $request = new CakeRequest($url);
  203. $Dispatch = new ControllerTestDispatcher();
  204. foreach (Router::$routes as $route) {
  205. if ($route instanceof RedirectRoute) {
  206. $route->response = $this->getMock('CakeResponse', array('send'));
  207. }
  208. }
  209. $Dispatch->loadRoutes = $this->loadRoutes;
  210. $request = $Dispatch->parseParams($request);
  211. if (!isset($request->params['controller'])) {
  212. $this->headers = Router::currentRoute()->response->header();
  213. return;
  214. }
  215. if ($this->__dirtyController) {
  216. $this->controller = null;
  217. }
  218. $plugin = empty($request->params['plugin']) ? '' : Inflector::camelize($request->params['plugin']) . '.';
  219. if ($this->controller === null && $this->autoMock) {
  220. $this->generate(Inflector::camelize($plugin . $request->params['controller']));
  221. }
  222. $params = array();
  223. if ($options['return'] == 'result') {
  224. $params['return'] = 1;
  225. $params['bare'] = 1;
  226. $params['requested'] = 1;
  227. }
  228. $Dispatch->testController = $this->controller;
  229. $Dispatch->response = $this->getMock('CakeResponse', array('send'));
  230. $this->result = $Dispatch->dispatch($request, $Dispatch->response, $params);
  231. $this->controller = $Dispatch->testController;
  232. if ($options['return'] != 'result') {
  233. if (isset($this->controller->View)) {
  234. $this->vars = $this->controller->View->viewVars;
  235. $this->view = $this->controller->View->_viewNoLayout;
  236. }
  237. $this->contents = $this->controller->response->body();
  238. }
  239. $this->__dirtyController = true;
  240. $this->headers = $Dispatch->response->header();
  241. return $this->{$options['return']};
  242. }
  243. /**
  244. * Generates a mocked controller and mocks any classes passed to `$mocks`. By
  245. * default, `_stop()` is stubbed as is sending the response headers, so to not
  246. * interfere with testing.
  247. *
  248. * ### Mocks:
  249. *
  250. * - `methods` Methods to mock on the controller. `_stop()` is mocked by default
  251. * - `models` Models to mock. Models are added to the ClassRegistry so they any
  252. * time they are instatiated the mock will be created. Pass as key value pairs
  253. * with the value being specific methods on the model to mock. If `true` or
  254. * no value is passed, the entire model will be mocked.
  255. * - `components` Components to mock. Components are only mocked on this controller
  256. * and not within each other (i.e., components on components)
  257. *
  258. * @param string $controller Controller name
  259. * @param array $mocks List of classes and methods to mock
  260. * @return Controller Mocked controller
  261. */
  262. public function generate($controller, $mocks = array()) {
  263. list($plugin, $controller) = pluginSplit($controller);
  264. if ($plugin) {
  265. App::uses($plugin . 'AppController', $plugin . '.Controller');
  266. $plugin .= '.';
  267. }
  268. App::uses($controller . 'Controller', $plugin . 'Controller');
  269. if (!class_exists($controller.'Controller')) {
  270. throw new MissingControllerException(array(
  271. 'class' => $controller . 'Controller',
  272. 'plugin' => substr($plugin, 0, -1)
  273. ));
  274. }
  275. ClassRegistry::flush();
  276. $mocks = array_merge_recursive(array(
  277. 'methods' => array('_stop'),
  278. 'models' => array(),
  279. 'components' => array()
  280. ), (array)$mocks);
  281. list($plugin, $name) = pluginSplit($controller);
  282. $_controller = $this->getMock($name.'Controller', $mocks['methods'], array(), '', false);
  283. $_controller->name = $name;
  284. $request = $this->getMock('CakeRequest');
  285. $response = $this->getMock('CakeResponse', array('_sendHeader'));
  286. $_controller->__construct($request, $response);
  287. $config = ClassRegistry::config('Model');
  288. foreach ($mocks['models'] as $model => $methods) {
  289. if (is_string($methods)) {
  290. $model = $methods;
  291. $methods = true;
  292. }
  293. if ($methods === true) {
  294. $methods = array();
  295. }
  296. ClassRegistry::init($model);
  297. list($plugin, $name) = pluginSplit($model);
  298. $config = array_merge((array)$config, array('name' => $model));
  299. $_model = $this->getMock($name, $methods, array($config));
  300. ClassRegistry::removeObject($name);
  301. ClassRegistry::addObject($name, $_model);
  302. }
  303. foreach ($mocks['components'] as $component => $methods) {
  304. if (is_string($methods)) {
  305. $component = $methods;
  306. $methods = true;
  307. }
  308. if ($methods === true) {
  309. $methods = array();
  310. }
  311. list($plugin, $name) = pluginSplit($component, true);
  312. $componentClass = $name . 'Component';
  313. App::uses($componentClass, $plugin . 'Controller/Component');
  314. if (!class_exists($componentClass)) {
  315. throw new MissingComponentException(array(
  316. 'class' => $componentClass
  317. ));
  318. }
  319. $_component = $this->getMock($componentClass, $methods, array(), '', false);
  320. $_controller->Components->set($name, $_component);
  321. }
  322. $_controller->constructClasses();
  323. $this->__dirtyController = false;
  324. $this->controller = $_controller;
  325. return $this->controller;
  326. }
  327. }