cakephp2-php8/lib/Cake/TestSuite/ControllerTestCase.php
mark_story 82d57fb8b2 Add mocked components to the enable list of components.
Mocked components should be enabled so they receive callbacks. This
allows more realistic integration tests using testAction(). Stop
disabling the constructor as it causes a number of errors with
components that use components.

Fixes #3842
2013-05-20 22:22:22 -04:00

378 lines
11 KiB
PHP

<?php
/**
* ControllerTestCase file
*
* PHP 5
*
* CakePHP(tm) Tests <http://book.cakephp.org/2.0/en/development/testing.html>
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests
* @package Cake.TestSuite
* @since CakePHP(tm) v 2.0
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
App::uses('Dispatcher', 'Routing');
App::uses('CakeTestCase', 'TestSuite');
App::uses('Router', 'Routing');
App::uses('CakeRequest', 'Network');
App::uses('CakeResponse', 'Network');
App::uses('Helper', 'View');
App::uses('CakeEvent', 'Event');
/**
* 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
*/
protected 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;
foreach ($this->testController->Components->loaded() as $component) {
$object = $this->testController->Components->{$component};
if (isset($object->response)) {
$object->response = $response;
}
if (isset($object->request)) {
$object->request = $request;
}
}
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->assign('__view_no_layout__', $this->_View->fetch('content'));
$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 = true;
/**
* 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
*/
protected $_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 the return of _testAction
* @throws BadMethodCallException when you call methods that don't exist.
*/
public function __call($name, $arguments) {
if ($name === 'testAction') {
return call_user_func_array(array($this, '_testAction'), $arguments);
}
throw new BadMethodCallException("Method '{$name}' does not exist.");
}
/**
* Lets you do functional tests of a controller action.
*
* ### Options:
*
* - `data` Will be used as the request data. If the `method` is GET,
* data will be used a GET params. If the `method` is POST, it will be used
* as POST data. By setting `$options['data']` to a string, you can simulate XML or JSON
* payloads to your controllers allowing you to test REST webservices.
* - `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
* @return mixed
*/
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);
$restore = array('get' => $_GET, 'post' => $_POST);
$_SERVER['REQUEST_METHOD'] = strtoupper($options['method']);
if (is_array($options['data'])) {
if (strtoupper($options['method']) === 'GET') {
$_GET = $options['data'];
$_POST = array();
} else {
$_POST = $options['data'];
$_GET = array();
}
}
$request = $this->getMock('CakeRequest', array('_readInput'), array($url));
if (is_string($options['data'])) {
$request->expects($this->any())
->method('_readInput')
->will($this->returnValue($options['data']));
}
$Dispatch = new ControllerTestDispatcher();
foreach (Router::$routes as $route) {
if ($route instanceof RedirectRoute) {
$route->response = $this->getMock('CakeResponse', array('send'));
}
}
$Dispatch->loadRoutes = $this->loadRoutes;
$Dispatch->parseParams(new CakeEvent('ControllerTestCase', $Dispatch, array('request' => $request)));
if (!isset($request->params['controller']) && Router::currentRoute()) {
$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($plugin . Inflector::camelize($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;
$this->vars = $this->controller->viewVars;
$this->contents = $this->controller->response->body();
if (isset($this->controller->View)) {
$this->view = $this->controller->View->fetch('__view_no_layout__');
}
$this->_dirtyController = true;
$this->headers = $Dispatch->response->header();
$_GET = $restore['get'];
$_POST = $restore['post'];
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 instantiated 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
* @throws MissingControllerException When controllers could not be created.
* @throws MissingComponentException When components could not be created.
*/
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);
$controllerObj = $this->getMock($name . 'Controller', $mocks['methods'], array(), '', false);
$controllerObj->name = $name;
$request = $this->getMock('CakeRequest');
$response = $this->getMock('CakeResponse', array('_sendHeader'));
$controllerObj->__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();
}
$this->getMockForModel($model, $methods, $config);
}
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
));
}
$componentObj = $this->getMock($componentClass, $methods, array($controllerObj->Components));
$controllerObj->Components->set($name, $componentObj);
$controllerObj->Components->enable($name);
}
$controllerObj->constructClasses();
$this->_dirtyController = false;
$this->controller = $controllerObj;
return $this->controller;
}
}