cakephp2-php8/lib/Cake/TestSuite/ControllerTestCase.php
mark_story c5ca10ca69 Remove controller reuse between testAction() calls.
Reusing controllers between various testAction calls would
mean that calls that needed to render views, would break on the
second call to testAction().  Controllers are rebuilt for each testAction()
call now.

Fixes #2142
2011-10-22 10:58:21 -04:00

349 lines
9.6 KiB
PHP

<?php
/**
* ControllerTestCase file
*
* PHP 5
*
* CakePHP(tm) Tests <http://book.cakephp.org/view/1196/Testing>
* Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice
*
* @copyright Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests
* @package Cake.TestSuite
* @since CakePHP(tm) v 2.0
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
PHP_CodeCoverage_Filter::getInstance()->addFileToBlacklist(__FILE__, 'DEFAULT');
App::uses('Dispatcher', 'Routing');
App::uses('CakeTestCase', 'TestSuite');
App::uses('Router', 'Routing');
App::uses('CakeRequest', 'Network');
App::uses('CakeResponse', 'Network');
App::uses('Helper', 'View');
/**
* ControllerTestDispatcher class
*
* @package Cake.TestSuite
*/
class ControllerTestDispatcher extends Dispatcher {
/**
* The controller to use in the dispatch process
*
* @var Controller
*/
public $testController = null;
/**
* Use custom routes during tests
*
* @var boolean
*/
public $loadRoutes = true;
/**
* Returns the test controller
*
* @return Controller
*/
function _getController($request, $response) {
if ($this->testController === null) {
$this->testController = parent::_getController($request, $response);
}
$this->testController->helpers = array_merge(array('InterceptContent'), $this->testController->helpers);
$this->testController->setRequest($request);
$this->testController->response = $this->response;
return $this->testController;
}
/**
* Loads routes and resets if the test case dictates it should
*
* @return void
*/
protected function _loadRoutes() {
parent::_loadRoutes();
if (!$this->loadRoutes) {
Router::reload();
}
}
}
/**
* InterceptContentHelper class
*
* @package Cake.TestSuite
*/
class InterceptContentHelper extends Helper {
/**
* Intercepts and stores the contents of the view before the layout is rendered
*
* @param string $viewFile The view file
*/
public function afterRender($viewFile) {
$this->_View->_viewNoLayout = $this->_View->output;
$this->_View->Helpers->unload('InterceptContent');
}
}
/**
* ControllerTestCase class
*
* @package Cake.TestSuite
*/
abstract class ControllerTestCase extends CakeTestCase {
/**
* The controller to test in testAction
*
* @var Controller
*/
public $controller = null;
/**
* Automatically mock controllers that aren't mocked
*
* @var boolean
*/
public $autoMock = false;
/**
* Use custom routes during tests
*
* @var boolean
*/
public $loadRoutes = true;
/**
* The resulting view vars of the last testAction call
*
* @var array
*/
public $vars = null;
/**
* The resulting rendered view of the last testAction call
*
* @var string
*/
public $view = null;
/**
* The resulting rendered layout+view of the last testAction call
*
* @var string
*/
public $contents = null;
/**
* The returned result of the dispatch (requestAction), if any
*
* @var string
*/
public $result = null;
/**
* The headers that would have been sent by the action
*
* @var string
*/
public $headers = null;
/**
* Flag for checking if the controller instance is dirty.
* Once a test has been run on a controller it should be rebuilt
* to clean up properties.
*
* @var boolean
*/
private $__dirtyController = false;
/**
* Used to enable calling ControllerTestCase::testAction() without the testing
* framework thinking that it's a test case
*
* @param string $name The name of the function
* @param array $arguments Array of arguments
* @return Function
*/
public function __call($name, $arguments) {
if ($name == 'testAction') {
return call_user_func_array(array($this, '_testAction'), $arguments);
}
}
/**
* Tests a controller action.
*
* ### Options:
*
* - `data` POST or GET data to pass. Depends on the method.
* - `method` POST or GET. Defaults to POST.
* - `return` Specify the return type you want. Choose from:
* - `vars` Get the set view variables.
* - `view` Get the rendered view, without a layout.
* - `contents` Get the rendered view including the layout.
* - `result` Get the return value of the controller action. Useful
* for testing requestAction methods.
*
* @param string $url The url to test
* @param array $options See options
*/
protected function _testAction($url = '', $options = array()) {
$this->vars = $this->result = $this->view = $this->contents = $this->headers = null;
$options = array_merge(array(
'data' => array(),
'method' => 'POST',
'return' => 'result'
), $options);
$_SERVER['REQUEST_METHOD'] = strtoupper($options['method']);
if (strtoupper($options['method']) == 'GET') {
$_GET = $options['data'];
$_POST = array();
} else {
$_POST = $options['data'];
$_GET = array();
}
$request = new CakeRequest($url);
$Dispatch = new ControllerTestDispatcher();
foreach (Router::$routes as $route) {
if ($route instanceof RedirectRoute) {
$route->response = $this->getMock('CakeResponse', array('send'));
}
}
$Dispatch->loadRoutes = $this->loadRoutes;
$request = $Dispatch->parseParams($request);
if (!isset($request->params['controller'])) {
$this->headers = Router::currentRoute()->response->header();
return;
}
if ($this->__dirtyController) {
$this->controller = null;
}
$plugin = empty($request->params['plugin']) ? '' : Inflector::camelize($request->params['plugin']) . '.';
if ($this->controller === null && $this->autoMock) {
$this->generate(Inflector::camelize($plugin . $request->params['controller']));
}
$params = array();
if ($options['return'] == 'result') {
$params['return'] = 1;
$params['bare'] = 1;
$params['requested'] = 1;
}
$Dispatch->testController = $this->controller;
$Dispatch->response = $this->getMock('CakeResponse', array('send'));
$this->result = $Dispatch->dispatch($request, $Dispatch->response, $params);
$this->controller = $Dispatch->testController;
if ($options['return'] != 'result') {
if (isset($this->controller->View)) {
$this->vars = $this->controller->View->viewVars;
$this->view = $this->controller->View->_viewNoLayout;
}
$this->contents = $this->controller->response->body();
}
$this->__dirtyController = true;
$this->headers = $Dispatch->response->header();
return $this->{$options['return']};
}
/**
* Generates a mocked controller and mocks any classes passed to `$mocks`. By
* default, `_stop()` is stubbed as is sending the response headers, so to not
* interfere with testing.
*
* ### Mocks:
*
* - `methods` Methods to mock on the controller. `_stop()` is mocked by default
* - `models` Models to mock. Models are added to the ClassRegistry so they any
* time they are instatiated the mock will be created. Pass as key value pairs
* with the value being specific methods on the model to mock. If `true` or
* no value is passed, the entire model will be mocked.
* - `components` Components to mock. Components are only mocked on this controller
* and not within each other (i.e., components on components)
*
* @param string $controller Controller name
* @param array $mocks List of classes and methods to mock
* @return Controller Mocked controller
*/
public function generate($controller, $mocks = array()) {
list($plugin, $controller) = pluginSplit($controller);
if ($plugin) {
App::uses($plugin . 'AppController', $plugin . '.Controller');
$plugin .= '.';
}
App::uses($controller . 'Controller', $plugin . 'Controller');
if (!class_exists($controller.'Controller')) {
throw new MissingControllerException(array(
'class' => $controller . 'Controller',
'plugin' => substr($plugin, 0, -1)
));
}
ClassRegistry::flush();
$mocks = array_merge_recursive(array(
'methods' => array('_stop'),
'models' => array(),
'components' => array()
), (array)$mocks);
list($plugin, $name) = pluginSplit($controller);
$_controller = $this->getMock($name.'Controller', $mocks['methods'], array(), '', false);
$_controller->name = $name;
$request = $this->getMock('CakeRequest');
$response = $this->getMock('CakeResponse', array('_sendHeader'));
$_controller->__construct($request, $response);
$config = ClassRegistry::config('Model');
foreach ($mocks['models'] as $model => $methods) {
if (is_string($methods)) {
$model = $methods;
$methods = true;
}
if ($methods === true) {
$methods = array();
}
ClassRegistry::init($model);
list($plugin, $name) = pluginSplit($model);
$config = array_merge((array)$config, array('name' => $model));
$_model = $this->getMock($name, $methods, array($config));
ClassRegistry::removeObject($name);
ClassRegistry::addObject($name, $_model);
}
foreach ($mocks['components'] as $component => $methods) {
if (is_string($methods)) {
$component = $methods;
$methods = true;
}
if ($methods === true) {
$methods = array();
}
list($plugin, $name) = pluginSplit($component, true);
$componentClass = $name . 'Component';
App::uses($componentClass, $plugin . 'Controller/Component');
if (!class_exists($componentClass)) {
throw new MissingComponentException(array(
'class' => $componentClass
));
}
$_component = $this->getMock($componentClass, $methods, array(), '', false);
$_controller->Components->set($name, $_component);
}
$_controller->constructClasses();
$this->__dirtyController = false;
$this->controller = $_controller;
return $this->controller;
}
}