7
* CakePHP(tm) Tests <http://book.cakephp.org/view/1196/Testing>
8
* Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
10
* Licensed under The Open Group Test Suite License
11
* Redistributions of files must retain the above copyright notice.
13
* @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
14
* @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests
16
* @subpackage cake.cake.tests.libs
17
* @since CakePHP(tm) v 1.2.0.4667
18
* @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License
20
if (!class_exists('dispatcher')) {
21
require CAKE . 'dispatcher.php';
23
require_once CAKE_TESTS_LIB . 'cake_test_model.php';
24
require_once CAKE_TESTS_LIB . 'cake_test_fixture.php';
25
App::import('Vendor', 'simpletest' . DS . 'unit_tester');
31
* @subpackage cake.cake.tests.lib
33
class CakeTestDispatcher extends Dispatcher {
47
* @param CakeTestCase $testCase
51
function testCase(&$testCase) {
52
$this->testCase =& $testCase;
58
* @param Controller $controller
59
* @param array $params
60
* @param boolean $missingAction
64
function _invoke(&$controller, $params, $missingAction = false) {
65
$this->controller =& $controller;
67
if (isset($this->testCase) && method_exists($this->testCase, 'startController')) {
68
$this->testCase->startController($this->controller, $params);
71
$result = parent::_invoke($this->controller, $params, $missingAction);
73
if (isset($this->testCase) && method_exists($this->testCase, 'endController')) {
74
$this->testCase->endController($this->controller, $params);
85
* @subpackage cake.cake.tests.lib
87
class CakeTestCase extends UnitTestCase {
90
* Methods used internally.
95
var $methods = array('start', 'end', 'startcase', 'endcase', 'starttest', 'endtest');
98
* By default, all fixtures attached to this class will be truncated and reloaded after each test.
99
* Set this to false to handle manually
104
var $autoFixtures = true;
107
* Set this to false to avoid tables to be dropped if they already exist
112
var $dropTables = true;
115
* Maps fixture class names to fixture identifiers as included in CakeTestCase::$fixtures
120
var $_fixtureClassMap = array();
128
var $__truncated = true;
131
* savedGetData property
136
var $__savedGetData = array();
139
* Called when a test case (group of methods) is about to start (to be overriden when needed.)
141
* @param string $method Test method about to get executed.
145
function startCase() {
149
* Called when a test case (group of methods) has been executed (to be overriden when needed.)
151
* @param string $method Test method about that was executed.
159
* Called when a test case method is about to start (to be overriden when needed.)
161
* @param string $method Test method about to get executed.
165
function startTest($method) {
169
* Called when a test case method has been executed (to be overriden when needed.)
171
* @param string $method Test method about that was executed.
175
function endTest($method) {
179
* Overrides SimpleTestCase::assert to enable calling of skipIf() from within tests
181
* @param Expectation $expectation
182
* @param mixed $compare
183
* @param string $message
184
* @return boolean|null
187
function assert(&$expectation, $compare, $message = '%s') {
188
if ($this->_should_skip) {
191
return parent::assert($expectation, $compare, $message);
195
* Overrides SimpleTestCase::skipIf to provide a boolean return value
197
* @param boolean $shouldSkip
198
* @param string $message
202
function skipIf($shouldSkip, $message = '%s') {
203
parent::skipIf($shouldSkip, $message);
208
* Callback issued when a controller's action is about to be invoked through testAction().
210
* @param Controller $controller Controller that's about to be invoked.
211
* @param array $params Additional parameters as sent by testAction().
215
function startController(&$controller, $params = array()) {
216
if (isset($params['fixturize']) && ((is_array($params['fixturize']) && !empty($params['fixturize'])) || $params['fixturize'] === true)) {
217
if (!isset($this->db)) {
221
if ($controller->uses === false) {
222
$list = array($controller->modelClass);
224
$list = is_array($controller->uses) ? $controller->uses : array($controller->uses);
228
ClassRegistry::config(array('ds' => $params['connection']));
230
foreach ($list as $name) {
231
if ((is_array($params['fixturize']) && in_array($name, $params['fixturize'])) || $params['fixturize'] === true) {
232
if (class_exists($name) || App::import('Model', $name)) {
233
$object =& ClassRegistry::init($name);
234
//switch back to specified datasource.
235
$object->setDataSource($params['connection']);
236
$db =& ConnectionManager::getDataSource($object->useDbConfig);
237
$db->cacheSources = false;
239
$models[$object->alias] = array(
240
'table' => $object->table,
241
'model' => $object->alias,
242
'key' => strtolower($name),
247
ClassRegistry::config(array('ds' => 'test_suite'));
249
if (!empty($models) && isset($this->db)) {
250
$this->_actionFixtures = array();
252
foreach ($models as $model) {
253
$fixture =& new CakeTestFixture($this->db);
255
$fixture->name = $model['model'] . 'Test';
256
$fixture->table = $model['table'];
257
$fixture->import = array('model' => $model['model'], 'records' => true);
260
$fixture->create($this->db);
261
$fixture->insert($this->db);
262
$this->_actionFixtures[] =& $fixture;
265
foreach ($models as $model) {
266
$object =& ClassRegistry::getObject($model['key']);
267
if ($object !== false) {
268
$object->setDataSource('test_suite');
269
$object->cacheSources = false;
277
* Callback issued when a controller's action has been invoked through testAction().
279
* @param Controller $controller Controller that has been invoked.
280
* @param array $params Additional parameters as sent by testAction().
284
function endController(&$controller, $params = array()) {
285
if (isset($this->db) && isset($this->_actionFixtures) && !empty($this->_actionFixtures) && $this->dropTables) {
286
foreach ($this->_actionFixtures as $fixture) {
287
$fixture->drop($this->db);
293
* Executes a Cake URL, and can get (depending on the $params['return'] value):
296
* - 'return' has several possible values:
297
* 1. 'result': Whatever the action returns (and also specifies $this->params['requested'] for controller)
298
* 2. 'view': The rendered view, without the layout
299
* 3. 'contents': The rendered view, within the layout.
300
* 4. 'vars': the view vars
302
* - 'fixturize' - Set to true if you want to copy model data from 'connection' to the test_suite connection
303
* - 'data' - The data you want to insert into $this->data in the controller.
304
* - 'connection' - Which connection to use in conjunction with fixturize (defaults to 'default')
305
* - 'method' - What type of HTTP method to simulate (defaults to post)
307
* @param string $url Cake URL to execute (e.g: /articles/view/455)
308
* @param mixed $params Parameters (see above), or simply a string of what to return
309
* @return mixed Whatever is returned depending of requested result
312
function testAction($url, $params = array()) {
314
'return' => 'result',
315
'fixturize' => false,
318
'connection' => 'default'
321
if (is_string($params)) {
322
$params = array('return' => $params);
324
$params = array_merge($default, $params);
334
$this->__savedGetData = (empty($this->__savedGetData))
335
? array_intersect_key($_GET, $toSave)
336
: $this->__savedGetData;
338
$data = (!empty($params['data'])) ? $params['data'] : array();
340
if (strtolower($params['method']) == 'get') {
341
$_GET = array_merge($this->__savedGetData, $data);
344
$_POST = array('data' => $data);
345
$_GET = $this->__savedGetData;
348
$return = $params['return'];
349
$params = array_diff_key($params, array('data' => null, 'method' => null, 'return' => null));
351
$dispatcher =& new CakeTestDispatcher();
352
$dispatcher->testCase($this);
354
if ($return != 'result') {
355
if ($return != 'contents') {
356
$params['layout'] = false;
360
@$dispatcher->dispatch($url, $params);
361
$result = ob_get_clean();
363
if ($return == 'vars') {
364
$view =& ClassRegistry::getObject('view');
365
$viewVars = $view->getVars();
369
foreach ($viewVars as $var) {
370
$result[$var] = $view->getVar($var);
373
if (!empty($view->pageTitle)) {
374
$result = array_merge($result, array('title' => $view->pageTitle));
378
$params['return'] = 1;
380
$params['requested'] = 1;
382
$result = @$dispatcher->dispatch($url, $params);
385
if (isset($this->_actionFixtures)) {
386
unset($this->_actionFixtures);
388
ClassRegistry::flush();
394
* Announces the start of a test.
396
* @param string $method Test method just started.
400
function before($method) {
401
parent::before($method);
403
if (isset($this->fixtures) && (!is_array($this->fixtures) || empty($this->fixtures))) {
404
unset($this->fixtures);
407
// Set up DB connection
408
if (isset($this->fixtures) && strtolower($method) == 'start') {
410
$this->_loadFixtures();
414
if (isset($this->_fixtures) && isset($this->db) && !in_array(strtolower($method), array('start', 'end')) && $this->__truncated && $this->autoFixtures == true) {
415
foreach ($this->_fixtures as $fixture) {
416
$inserts = $fixture->insert($this->db);
420
if (!in_array(strtolower($method), $this->methods)) {
421
$this->startTest($method);
426
* Runs as first test to create tables.
432
if (isset($this->_fixtures) && isset($this->db)) {
433
Configure::write('Cache.disable', true);
434
$cacheSources = $this->db->cacheSources;
435
$this->db->cacheSources = false;
436
$sources = $this->db->listSources();
437
$this->db->cacheSources = $cacheSources;
439
if (!$this->dropTables) {
442
foreach ($this->_fixtures as $fixture) {
443
$table = $this->db->config['prefix'] . $fixture->table;
444
if (in_array($table, $sources)) {
445
$fixture->drop($this->db);
446
$fixture->create($this->db);
447
} elseif (!in_array($table, $sources)) {
448
$fixture->create($this->db);
455
* Runs as last test to drop tables.
461
if (isset($this->_fixtures) && isset($this->db)) {
462
if ($this->dropTables) {
463
foreach (array_reverse($this->_fixtures) as $fixture) {
464
$fixture->drop($this->db);
467
$this->db->sources(true);
468
Configure::write('Cache.disable', false);
471
if (class_exists('ClassRegistry')) {
472
ClassRegistry::flush();
477
* Announces the end of a test.
479
* @param string $method Test method just finished.
483
function after($method) {
484
$isTestMethod = !in_array(strtolower($method), array('start', 'end'));
486
if (isset($this->_fixtures) && isset($this->db) && $isTestMethod) {
487
foreach ($this->_fixtures as $fixture) {
488
$fixture->truncate($this->db);
490
$this->__truncated = true;
492
$this->__truncated = false;
495
if (!in_array(strtolower($method), $this->methods)) {
496
$this->endTest($method);
498
$this->_should_skip = false;
500
parent::after($method);
504
* Gets a list of test names. Normally that will be all internal methods that start with the
505
* name "test". This method should be overridden if you want a different rule.
507
* @return array List of test names.
510
function getTests() {
512
array('start', 'startCase'),
513
array_diff(parent::getTests(), array('testAction', 'testaction')),
514
array('endCase', 'end')
519
* Chooses which fixtures to load for a given test
521
* @param string $fixture Each parameter is a model name that corresponds to a
522
* fixture, i.e. 'Post', 'Author', etc.
525
* @see CakeTestCase::$autoFixtures
527
function loadFixtures() {
528
$args = func_get_args();
529
foreach ($args as $class) {
530
if (isset($this->_fixtureClassMap[$class])) {
531
$fixture = $this->_fixtures[$this->_fixtureClassMap[$class]];
533
$fixture->truncate($this->db);
534
$fixture->insert($this->db);
536
trigger_error(sprintf(__('Referenced fixture class %s not found', true), $class), E_USER_WARNING);
542
* Takes an array $expected and generates a regex from it to match the provided $string.
543
* Samples for $expected:
545
* Checks for an input tag with a name attribute (contains any non-empty value) and an id
546
* attribute that contains 'my-input':
547
* array('input' => array('name', 'id' => 'my-input'))
549
* Checks for two p elements with some text in them:
551
* array('p' => true),
554
* array('p' => true),
559
* You can also specify a pattern expression as part of the attribute values, or the tag
560
* being defined, if you prepend the value with preg: and enclose it with slashes, like so:
562
* array('input' => array('name', 'id' => 'preg:/FieldName\d+/')),
563
* 'preg:/My\s+field/'
566
* Important: This function is very forgiving about whitespace and also accepts any
567
* permutation of attribute order. It will also allow whitespaces between specified tags.
569
* @param string $string An HTML/XHTML/XML string
570
* @param array $expected An array, see above
571
* @param string $message SimpleTest failure output string
575
function assertTags($string, $expected, $fullDebug = false) {
577
$normalized = array();
578
foreach ((array) $expected as $key => $val) {
579
if (!is_numeric($key)) {
580
$normalized[] = array($key => $val);
582
$normalized[] = $val;
586
foreach ($normalized as $tags) {
587
if (!is_array($tags)) {
588
$tags = (string)$tags;
591
if (is_string($tags) && $tags{0} == '<') {
592
$tags = array(substr($tags, 1) => array());
593
} elseif (is_string($tags)) {
594
$tagsTrimmed = preg_replace('/\s+/m', '', $tags);
596
if (preg_match('/^\*?\//', $tags, $match) && $tagsTrimmed !== '//') {
597
$prefix = array(null, null);
599
if ($match[0] == '*/') {
600
$prefix = array('Anything, ', '.*?');
603
sprintf('%sClose %s tag', $prefix[0], substr($tags, strlen($match[0]))),
604
sprintf('%s<[\s]*\/[\s]*%s[\s]*>[\n\r]*', $prefix[1], substr($tags, strlen($match[0]))),
609
if (!empty($tags) && preg_match('/^preg\:\/(.+)\/$/i', $tags, $matches)) {
611
$type = 'Regex matches';
613
$tags = preg_quote($tags, '/');
614
$type = 'Text equals';
617
sprintf('%s "%s"', $type, $tags),
623
foreach ($tags as $tag => $attributes) {
625
sprintf('Open %s tag', $tag),
626
sprintf('[\s]*<%s', preg_quote($tag, '/')),
629
if ($attributes === true) {
630
$attributes = array();
633
$explanations = array();
635
foreach ($attributes as $attr => $val) {
636
if (is_numeric($attr) && preg_match('/^preg\:\/(.+)\/$/i', $val, $matches)) {
637
$attrs[] = $matches[1];
638
$explanations[] = sprintf('Regex "%s" matches', $matches[1]);
642
if (is_numeric($attr)) {
645
$explanations[] = sprintf('Attribute "%s" present', $attr);
646
} elseif (!empty($val) && preg_match('/^preg\:\/(.+)\/$/i', $val, $matches)) {
649
$explanations[] = sprintf('Attribute "%s" matches "%s"', $attr, $val);
651
$explanations[] = sprintf('Attribute "%s" == "%s"', $attr, $val);
652
$val = preg_quote($val, '/');
654
$attrs[] = '[\s]+' . preg_quote($attr, '/') . '=' . $quotes . $val . $quotes;
659
$permutations = $this->__array_permute($attrs);
661
$permutationTokens = array();
662
foreach ($permutations as $permutation) {
663
$permutationTokens[] = implode('', $permutation);
666
sprintf('%s', implode(', ', $explanations)),
672
sprintf('End %s tag', $tag),
673
'[\s]*\/?[\s]*>[\n\r]*',
678
foreach ($regex as $i => $assertation) {
679
list($description, $expressions, $itemNum) = $assertation;
681
foreach ((array)$expressions as $expression) {
682
if (preg_match(sprintf('/^%s/s', $expression), $string, $match)) {
684
$string = substr($string, strlen($match[0]));
689
$this->assert(new TrueExpectation(), false, sprintf('Item #%d / regex #%d failed: %s', $itemNum, $i, $description));
691
debug($string, true);
697
return $this->assert(new TrueExpectation(), true, '%s');
701
* Initialize DB connection.
707
$testDbAvailable = in_array('test', array_keys(ConnectionManager::enumConnectionObjects()));
711
if ($testDbAvailable) {
713
restore_error_handler();
714
@$db =& ConnectionManager::getDataSource('test');
715
set_error_handler('simpleTestErrorHandler');
716
$testDbAvailable = $db->isConnected();
719
// Try for default DB
720
if (!$testDbAvailable) {
721
$db =& ConnectionManager::getDataSource('default');
722
$_prefix = $db->config['prefix'];
723
$db->config['prefix'] = 'test_suite_';
726
ConnectionManager::create('test_suite', $db->config);
727
$db->config['prefix'] = $_prefix;
730
$this->db =& ConnectionManager::getDataSource('test_suite');
731
$this->db->cacheSources = false;
733
ClassRegistry::config(array('ds' => 'test_suite'));
737
* Load fixtures specified in var $fixtures.
742
function _loadFixtures() {
743
if (!isset($this->fixtures) || empty($this->fixtures)) {
747
if (!is_array($this->fixtures)) {
748
$this->fixtures = array_map('trim', explode(',', $this->fixtures));
751
$this->_fixtures = array();
753
foreach ($this->fixtures as $index => $fixture) {
756
if (strpos($fixture, 'core.') === 0) {
757
$fixture = substr($fixture, strlen('core.'));
758
foreach (App::core('cake') as $key => $path) {
759
$fixturePaths[] = $path . 'tests' . DS . 'fixtures';
761
} elseif (strpos($fixture, 'app.') === 0) {
762
$fixture = substr($fixture, strlen('app.'));
763
$fixturePaths = array(
765
VENDORS . 'tests' . DS . 'fixtures'
767
} elseif (strpos($fixture, 'plugin.') === 0) {
768
$parts = explode('.', $fixture, 3);
769
$pluginName = $parts[1];
770
$fixture = $parts[2];
771
$fixturePaths = array(
772
App::pluginPath($pluginName) . 'tests' . DS . 'fixtures',
774
VENDORS . 'tests' . DS . 'fixtures'
777
$fixturePaths = array(
779
VENDORS . 'tests' . DS . 'fixtures',
780
TEST_CAKE_CORE_INCLUDE_PATH . DS . 'cake' . DS . 'tests' . DS . 'fixtures'
784
foreach ($fixturePaths as $path) {
785
if (is_readable($path . DS . $fixture . '_fixture.php')) {
786
$fixtureFile = $path . DS . $fixture . '_fixture.php';
791
if (isset($fixtureFile)) {
792
require_once($fixtureFile);
793
$fixtureClass = Inflector::camelize($fixture) . 'Fixture';
794
$this->_fixtures[$this->fixtures[$index]] =& new $fixtureClass($this->db);
795
$this->_fixtureClassMap[Inflector::camelize($fixture)] = $this->fixtures[$index];
799
if (empty($this->_fixtures)) {
800
unset($this->_fixtures);
805
* Generates all permutation of an array $items and returns them in a new array.
807
* @param array $items An array of items
811
function __array_permute($items, $perms = array()) {
818
$permuted[] = $perms;
820
$numItems = count($items) - 1;
821
for ($i = $numItems; $i >= 0; --$i) {
824
list($tmp) = array_splice($newItems, $i, 1);
825
array_unshift($newPerms, $tmp);
826
$this->__array_permute($newItems, $newPerms);