CakePHP
  • Documentation
    • Book
    • API
    • Videos
    • Reporting Security Issues
    • Privacy Policy
    • Logos & Trademarks
  • Business Solutions
  • Swag
  • Road Trip
  • Team
  • Community
    • Community
    • Get Involved
    • Issues (GitHub)
    • Bakery
    • Featured Resources
    • Training
    • Meetups
    • My CakePHP
    • CakeFest
    • Newsletter
    • Linkedin
    • YouTube
    • Facebook
    • Twitter
    • Mastodon
    • Help & Support
    • Forum
    • Stack Overflow
    • Slack
    • Paid Support
CakePHP

C CakePHP 2.1 API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 2.1
      • 4.2
      • 4.1
      • 4.0
      • 3.9
      • 3.8
      • 3.7
      • 3.6
      • 3.5
      • 3.4
      • 3.3
      • 3.2
      • 3.1
      • 3.0
      • 2.10
      • 2.9
      • 2.8
      • 2.7
      • 2.6
      • 2.5
      • 2.4
      • 2.3
      • 2.2
      • 2.1
      • 2.0
      • 1.3
      • 1.2

Packages

  • Cake
    • Cache
      • Engine
    • Configure
    • Console
      • Command
        • Task
    • Controller
      • Component
        • Acl
        • Auth
    • Core
    • Error
    • Event
    • I18n
    • Log
      • Engine
    • Model
      • Behavior
      • Datasource
        • Database
        • Session
    • Network
      • Email
      • Http
    • Routing
      • Route
    • TestSuite
      • Coverage
      • Fixture
      • Reporter
    • Utility
    • View
      • Helper

Classes

  • BakeTask
  • ControllerTask
  • DbConfigTask
  • ExtractTask
  • FixtureTask
  • ModelTask
  • PluginTask
  • ProjectTask
  • TemplateTask
  • TestTask
  • ViewTask
  1: <?php
  2: /**
  3:  * The TestTask handles creating and updating test files.
  4:  *
  5:  * PHP 5
  6:  *
  7:  * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  8:  * Copyright 2005-2012, 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-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
 14:  * @link          http://cakephp.org CakePHP(tm) Project
 15:  * @since         CakePHP(tm) v 1.3
 16:  * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
 17:  */
 18: 
 19: App::uses('AppShell', 'Console/Command');
 20: App::uses('BakeTask', 'Console/Command/Task');
 21: App::uses('ClassRegistry', 'Utility');
 22: 
 23: /**
 24:  * Task class for creating and updating test files.
 25:  *
 26:  * @package       Cake.Console.Command.Task
 27:  */
 28: class TestTask extends BakeTask {
 29: 
 30: /**
 31:  * path to TESTS directory
 32:  *
 33:  * @var string
 34:  */
 35:     public $path = TESTS;
 36: 
 37: /**
 38:  * Tasks used.
 39:  *
 40:  * @var array
 41:  */
 42:     public $tasks = array('Template');
 43: 
 44: /**
 45:  * class types that methods can be generated for
 46:  *
 47:  * @var array
 48:  */
 49:     public $classTypes = array(
 50:         'Model' => 'Model',
 51:         'Controller' => 'Controller',
 52:         'Component' => 'Controller/Component',
 53:         'Behavior' => 'Model/Behavior',
 54:         'Helper' => 'View/Helper'
 55:     );
 56: 
 57: /**
 58:  * Internal list of fixtures that have been added so far.
 59:  *
 60:  * @var array
 61:  */
 62:     protected $_fixtures = array();
 63: 
 64: /**
 65:  * Execution method always used for tasks
 66:  *
 67:  * @return void
 68:  */
 69:     public function execute() {
 70:         parent::execute();
 71:         if (empty($this->args)) {
 72:             $this->_interactive();
 73:         }
 74: 
 75:         if (count($this->args) == 1) {
 76:             $this->_interactive($this->args[0]);
 77:         }
 78: 
 79:         if (count($this->args) > 1) {
 80:             $type = Inflector::underscore($this->args[0]);
 81:             if ($this->bake($type, $this->args[1])) {
 82:                 $this->out('<success>Done</success>');
 83:             }
 84:         }
 85:     }
 86: 
 87: /**
 88:  * Handles interactive baking
 89:  *
 90:  * @param string $type
 91:  * @return string|boolean
 92:  */
 93:     protected function _interactive($type = null) {
 94:         $this->interactive = true;
 95:         $this->hr();
 96:         $this->out(__d('cake_console', 'Bake Tests'));
 97:         $this->out(__d('cake_console', 'Path: %s', $this->getPath()));
 98:         $this->hr();
 99: 
100:         if ($type) {
101:             $type = Inflector::camelize($type);
102:             if (!isset($this->classTypes[$type])) {
103:                 $this->error(__d('cake_console', 'Incorrect type provided. Please choose one of %s', implode(', ', array_keys($this->classTypes))));
104:             }
105:         } else {
106:             $type = $this->getObjectType();
107:         }
108:         $className = $this->getClassName($type);
109:         return $this->bake($type, $className);
110:     }
111: 
112: /**
113:  * Completes final steps for generating data to create test case.
114:  *
115:  * @param string $type Type of object to bake test case for ie. Model, Controller
116:  * @param string $className the 'cake name' for the class ie. Posts for the PostsController
117:  * @return string|boolean
118:  */
119:     public function bake($type, $className) {
120:         $plugin = null;
121:         if ($this->plugin) {
122:             $plugin = $this->plugin . '.';
123:         }
124: 
125:         $realType = $this->mapType($type, $plugin);
126:         $fullClassName = $this->getRealClassName($type, $className);
127: 
128:         if ($this->typeCanDetectFixtures($type) && $this->isLoadableClass($realType, $fullClassName)) {
129:             $this->out(__d('cake_console', 'Bake is detecting possible fixtures...'));
130:             $testSubject = $this->buildTestSubject($type, $className);
131:             $this->generateFixtureList($testSubject);
132:         } elseif ($this->interactive) {
133:             $this->getUserFixtures();
134:         }
135:         App::uses($fullClassName, $realType);
136: 
137:         $methods = array();
138:         if (class_exists($fullClassName)) {
139:             $methods = $this->getTestableMethods($fullClassName);
140:         }
141:         $mock = $this->hasMockClass($type, $fullClassName);
142:         list($preConstruct, $construction, $postConstruct) = $this->generateConstructor($type, $fullClassName);
143:         $uses = $this->generateUses($type, $realType, $fullClassName);
144: 
145:         $this->out("\n" . __d('cake_console', 'Baking test case for %s %s ...', $className, $type), 1, Shell::QUIET);
146: 
147:         $this->Template->set('fixtures', $this->_fixtures);
148:         $this->Template->set('plugin', $plugin);
149:         $this->Template->set(compact(
150:             'className', 'methods', 'type', 'fullClassName', 'mock',
151:             'realType', 'preConstruct', 'postConstruct', 'construction',
152:             'uses'
153:         ));
154:         $out = $this->Template->generate('classes', 'test');
155: 
156:         $filename = $this->testCaseFileName($type, $className);
157:         $made = $this->createFile($filename, $out);
158:         if ($made) {
159:             return $out;
160:         }
161:         return false;
162:     }
163: 
164: /**
165:  * Interact with the user and get their chosen type. Can exit the script.
166:  *
167:  * @return string Users chosen type.
168:  */
169:     public function getObjectType() {
170:         $this->hr();
171:         $this->out(__d('cake_console', 'Select an object type:'));
172:         $this->hr();
173: 
174:         $keys = array();
175:         $i = 0;
176:         foreach ($this->classTypes as $option => $package) {
177:             $this->out(++$i . '. ' . $option);
178:             $keys[] = $i;
179:         }
180:         $keys[] = 'q';
181:         $selection = $this->in(__d('cake_console', 'Enter the type of object to bake a test for or (q)uit'), $keys, 'q');
182:         if ($selection == 'q') {
183:             return $this->_stop();
184:         }
185:         $types = array_keys($this->classTypes);
186:         return $types[$selection - 1];
187:     }
188: 
189: /**
190:  * Get the user chosen Class name for the chosen type
191:  *
192:  * @param string $objectType Type of object to list classes for i.e. Model, Controller.
193:  * @return string Class name the user chose.
194:  */
195:     public function getClassName($objectType) {
196:         $type = ucfirst(strtolower($objectType));
197:         $typeLength = strlen($type);
198:         $type = $this->classTypes[$type];
199:         if ($this->plugin) {
200:             $plugin = $this->plugin . '.';
201:             $options = App::objects($plugin . $type);
202:         } else {
203:             $options = App::objects($type);
204:         }
205:         $this->out(__d('cake_console', 'Choose a %s class', $objectType));
206:         $keys = array();
207:         foreach ($options as $key => $option) {
208:             $this->out(++$key . '. ' . $option);
209:             $keys[] = $key;
210:         }
211:         while (empty($selection)) {
212:             $selection = $this->in(__d('cake_console', 'Choose an existing class, or enter the name of a class that does not exist'));
213:             if (is_numeric($selection) && isset($options[$selection - 1])) {
214:                 $selection = $options[$selection - 1];
215:             }
216:             if ($type !== 'Model') {
217:                 $selection = substr($selection, 0, $typeLength * - 1);
218:             }
219:         }
220:         return $selection;
221:     }
222: 
223: /**
224:  * Checks whether the chosen type can find its own fixtures.
225:  * Currently only model, and controller are supported
226:  *
227:  * @param string $type The Type of object you are generating tests for eg. controller
228:  * @return boolean
229:  */
230:     public function typeCanDetectFixtures($type) {
231:         $type = strtolower($type);
232:         return in_array($type, array('controller', 'model'));
233:     }
234: 
235: /**
236:  * Check if a class with the given package is loaded or can be loaded.
237:  *
238:  * @param string $package The package of object you are generating tests for eg. controller
239:  * @param string $class the Classname of the class the test is being generated for.
240:  * @return boolean
241:  */
242:     public function isLoadableClass($package, $class) {
243:         App::uses($class, $package);
244:         return class_exists($class);
245:     }
246: 
247: /**
248:  * Construct an instance of the class to be tested.
249:  * So that fixtures can be detected
250:  *
251:  * @param string $type The Type of object you are generating tests for eg. controller
252:  * @param string $class the Classname of the class the test is being generated for.
253:  * @return object And instance of the class that is going to be tested.
254:  */
255:     public function &buildTestSubject($type, $class) {
256:         ClassRegistry::flush();
257:         App::import($type, $class);
258:         $class = $this->getRealClassName($type, $class);
259:         if (strtolower($type) == 'model') {
260:             $instance = ClassRegistry::init($class);
261:         } else {
262:             $instance = new $class();
263:         }
264:         return $instance;
265:     }
266: 
267: /**
268:  * Gets the real class name from the cake short form. If the class name is already
269:  * suffixed with the type, the type will not be duplicated.
270:  *
271:  * @param string $type The Type of object you are generating tests for eg. controller
272:  * @param string $class the Classname of the class the test is being generated for.
273:  * @return string Real classname
274:  */
275:     public function getRealClassName($type, $class) {
276:         if (strtolower($type) == 'model' || empty($this->classTypes[$type])) {
277:             return $class;
278:         }
279: 
280:         $position = strpos($class, $type);
281: 
282:         if ($position !== false && strlen($class) - $position == strlen($type)) {
283:             return $class;
284:         }
285:         return $class . $type;
286:     }
287: 
288: /**
289:  * Map the types that TestTask uses to concrete types that App::uses can use.
290:  *
291:  * @param string $type The type of thing having a test generated.
292:  * @param string $plugin The plugin name.
293:  * @return string
294:  * @throws CakeException When invalid object types are requested.
295:  */
296:     public function mapType($type, $plugin) {
297:         $type = ucfirst($type);
298:         if (empty($this->classTypes[$type])) {
299:             throw new CakeException(__d('cake_dev', 'Invalid object type.'));
300:         }
301:         $real = $this->classTypes[$type];
302:         if ($plugin) {
303:             $real = trim($plugin, '.') . '.' . $real;
304:         }
305:         return $real;
306:     }
307: 
308: /**
309:  * Get methods declared in the class given.
310:  * No parent methods will be returned
311:  *
312:  * @param string $className Name of class to look at.
313:  * @return array Array of method names.
314:  */
315:     public function getTestableMethods($className) {
316:         $classMethods = get_class_methods($className);
317:         $parentMethods = get_class_methods(get_parent_class($className));
318:         $thisMethods = array_diff($classMethods, $parentMethods);
319:         $out = array();
320:         foreach ($thisMethods as $method) {
321:             if (substr($method, 0, 1) != '_' && $method != strtolower($className)) {
322:                 $out[] = $method;
323:             }
324:         }
325:         return $out;
326:     }
327: 
328: /**
329:  * Generate the list of fixtures that will be required to run this test based on
330:  * loaded models.
331:  *
332:  * @param object $subject The object you want to generate fixtures for.
333:  * @return array Array of fixtures to be included in the test.
334:  */
335:     public function generateFixtureList($subject) {
336:         $this->_fixtures = array();
337:         if (is_a($subject, 'Model')) {
338:             $this->_processModel($subject);
339:         } elseif (is_a($subject, 'Controller')) {
340:             $this->_processController($subject);
341:         }
342:         return array_values($this->_fixtures);
343:     }
344: 
345: /**
346:  * Process a model recursively and pull out all the
347:  * model names converting them to fixture names.
348:  *
349:  * @param Model $subject A Model class to scan for associations and pull fixtures off of.
350:  * @return void
351:  */
352:     protected function _processModel($subject) {
353:         $this->_addFixture($subject->name);
354:         $associated = $subject->getAssociated();
355:         foreach ($associated as $alias => $type) {
356:             $className = $subject->{$alias}->name;
357:             if (!isset($this->_fixtures[$className])) {
358:                 $this->_processModel($subject->{$alias});
359:             }
360:             if ($type == 'hasAndBelongsToMany') {
361:                 if (!empty($subject->hasAndBelongsToMany[$alias]['with'])) {
362:                     list($plugin, $joinModel) = pluginSplit($subject->hasAndBelongsToMany[$alias]['with']);
363:                 } else {
364:                     $joinModel = Inflector::classify($subject->hasAndBelongsToMany[$alias]['joinTable']);
365:                 }
366:                 if (!isset($this->_fixtures[$joinModel])) {
367:                     $this->_processModel($subject->{$joinModel});
368:                 }
369:             }
370:         }
371:     }
372: 
373: /**
374:  * Process all the models attached to a controller
375:  * and generate a fixture list.
376:  *
377:  * @param Controller $subject A controller to pull model names off of.
378:  * @return void
379:  */
380:     protected function _processController($subject) {
381:         $subject->constructClasses();
382:         $models = array(Inflector::classify($subject->name));
383:         if (!empty($subject->uses)) {
384:             $models = $subject->uses;
385:         }
386:         foreach ($models as $model) {
387:             $this->_processModel($subject->{$model});
388:         }
389:     }
390: 
391: /**
392:  * Add classname to the fixture list.
393:  * Sets the app. or plugin.plugin_name. prefix.
394:  *
395:  * @param string $name Name of the Model class that a fixture might be required for.
396:  * @return void
397:  */
398:     protected function _addFixture($name) {
399:         $parent = get_parent_class($name);
400:         $prefix = 'app.';
401:         if (strtolower($parent) != 'appmodel' && strtolower(substr($parent, -8)) == 'appmodel') {
402:             $pluginName = substr($parent, 0, -8);
403:             $prefix = 'plugin.' . Inflector::underscore($pluginName) . '.';
404:         }
405:         $fixture = $prefix . Inflector::underscore($name);
406:         $this->_fixtures[$name] = $fixture;
407:     }
408: 
409: /**
410:  * Interact with the user to get additional fixtures they want to use.
411:  *
412:  * @return array Array of fixtures the user wants to add.
413:  */
414:     public function getUserFixtures() {
415:         $proceed = $this->in(__d('cake_console', 'Bake could not detect fixtures, would you like to add some?'), array('y', 'n'), 'n');
416:         $fixtures = array();
417:         if (strtolower($proceed) == 'y') {
418:             $fixtureList = $this->in(__d('cake_console', "Please provide a comma separated list of the fixtures names you'd like to use.\nExample: 'app.comment, app.post, plugin.forums.post'"));
419:             $fixtureListTrimmed = str_replace(' ', '', $fixtureList);
420:             $fixtures = explode(',', $fixtureListTrimmed);
421:         }
422:         $this->_fixtures = array_merge($this->_fixtures, $fixtures);
423:         return $fixtures;
424:     }
425: 
426: /**
427:  * Is a mock class required for this type of test?
428:  * Controllers require a mock class.
429:  *
430:  * @param string $type The type of object tests are being generated for eg. controller.
431:  * @return boolean
432:  */
433:     public function hasMockClass($type) {
434:         $type = strtolower($type);
435:         return $type == 'controller';
436:     }
437: 
438: /**
439:  * Generate a constructor code snippet for the type and classname
440:  *
441:  * @param string $type The Type of object you are generating tests for eg. controller
442:  * @param string $fullClassName The Classname of the class the test is being generated for.
443:  * @return array Constructor snippets for the thing you are building.
444:  */
445:     public function generateConstructor($type, $fullClassName) {
446:         $type = strtolower($type);
447:         $pre = $post = '';
448:         if ($type == 'model') {
449:             $construct = "ClassRegistry::init('$fullClassName');\n";
450:         }
451:         if ($type == 'behavior') {
452:             $construct = "new $fullClassName();\n";
453:         }
454:         if ($type == 'controller') {
455:             $className = substr($fullClassName, 0, -10);
456:             $construct = "new Test$fullClassName();\n";
457:             $post = "\$this->{$className}->constructClasses();\n";
458:         }
459:         if ($type == 'helper') {
460:             $pre = "\$View = new View();\n";
461:             $construct = "new {$fullClassName}(\$View);\n";
462:         }
463:         if ($type == 'component') {
464:             $pre = "\$Collection = new ComponentCollection();\n";
465:             $construct = "new {$fullClassName}(\$Collection);\n";
466:         }
467:         return array($pre, $construct, $post);
468:     }
469: 
470: /**
471:  * Generate the uses() calls for a type & classname
472:  *
473:  * @param string $type The Type of object you are generating tests for eg. controller
474:  * @param string $realType The package name for the class.
475:  * @param string $className The Classname of the class the test is being generated for.
476:  * @return array An array containing used classes
477:  */
478:     public function generateUses($type, $realType, $className) {
479:         $uses = array();
480:         if ($type == 'component') {
481:             $uses[] = array('ComponentCollection', 'Controller');
482:             $uses[] = array('Component', 'Controller');
483:         }
484:         if ($type == 'helper') {
485:             $uses[] = array('View', 'View');
486:             $uses[] = array('Helper', 'View');
487:         }
488:         $uses[] = array($className, $realType);
489:         return $uses;
490:     }
491: 
492: /**
493:  * Make the filename for the test case. resolve the suffixes for controllers
494:  * and get the plugin path if needed.
495:  *
496:  * @param string $type The Type of object you are generating tests for eg. controller
497:  * @param string $className the Classname of the class the test is being generated for.
498:  * @return string filename the test should be created on.
499:  */
500:     public function testCaseFileName($type, $className) {
501:         $path = $this->getPath() . 'Case' . DS;
502:         $type = Inflector::camelize($type);
503:         if (isset($this->classTypes[$type])) {
504:             $path .= $this->classTypes[$type] . DS;
505:         }
506:         $className = $this->getRealClassName($type, $className);
507:         return str_replace('/', DS, $path) . Inflector::camelize($className) . 'Test.php';
508:     }
509: 
510: /**
511:  * get the option parser.
512:  *
513:  * @return void
514:  */
515:     public function getOptionParser() {
516:         $parser = parent::getOptionParser();
517:         return $parser->description(__d('cake_console', 'Bake test case skeletons for classes.'))
518:             ->addArgument('type', array(
519:                 'help' => __d('cake_console', 'Type of class to bake, can be any of the following: controller, model, helper, component or behavior.'),
520:                 'choices' => array(
521:                     'Controller', 'controller',
522:                     'Model', 'model',
523:                     'Helper', 'helper',
524:                     'Component', 'component',
525:                     'Behavior', 'behavior'
526:                 )
527:             ))->addArgument('name', array(
528:                 'help' => __d('cake_console', 'An existing class to bake tests for.')
529:             ))->addOption('plugin', array(
530:                 'short' => 'p',
531:                 'help' => __d('cake_console', 'CamelCased name of the plugin to bake tests for.')
532:             ))->epilog(__d('cake_console', 'Omitting all arguments and options will enter into an interactive mode.'));
533:     }
534: 
535: }
536: 
OpenHub
Rackspace
Rackspace
  • Business Solutions
  • Showcase
  • Documentation
  • Book
  • API
  • Videos
  • Reporting Security Issues
  • Privacy Policy
  • Logos & Trademarks
  • Community
  • Get Involved
  • Issues (GitHub)
  • Bakery
  • Featured Resources
  • Training
  • Meetups
  • My CakePHP
  • CakeFest
  • Newsletter
  • Linkedin
  • YouTube
  • Facebook
  • Twitter
  • Mastodon
  • Help & Support
  • Forum
  • Stack Overflow
  • Slack
  • Paid Support

Generated using CakePHP API Docs