First things first
The base namespace should be Contrast\Test , which also means that cookies should be added to your composer.json files autoload and autoload-dev sections, as well as to baked composer.json plugin files. If you refuse to bake to edit yout composer.json file, you must add an autoload entry manually
"autoload": { "psr-4": { // ... "Contrast\\": "./plugins/Contrast/src" } }, "autoload-dev": { "psr-4": { // ... "Contrast\\Test\\": "./plugins/Contrast/tests" } },
and reset the autoloader again
$ composer dump-autoload
So, the namespace for your test case should be Contrast\Test\TestCase .
In addition, test files must be added using Test so that PHPUnit can recognize them. And in order for the files to be autoload, you must adhere to PSR-4, i.e. the files must have the same name as the class, i.e. Your test file should be named TextColorTest.php , not Color.php .
Testing as part of the application
When testing the plugin as part of the application, there is no need for the bootstrap file for plugin tests (bake should actually generate one), since you could run them with the configuration of your application, which has a test boot file ( tests/bootstrap.php ), which includes a bootstrap file for your applications ( config/bootstrap.php ).
Therefore, the tests will run from your base application folder and you will need to go through the path to the plugin, for example
$ vendor/bin/phpunit plugins/Contrast
or you add an additional plugin testsuite to your main phpunit configuration file, where <!-- Add your plugin suites -->
<testsuite name="Contrast Test Suite"> <directory>./plugins/Contrast/tests/TestCase</directory> </testsuite>
In this way, plugin tests will run along with your applications.
Finally, you can also run tests from the plugin directory, given that there is a corresponding bootstrap file. By default, it looks like this:
<?php $findRoot = function ($root) { do { $lastRoot = $root; $root = dirname($root); if (is_dir($root . '/vendor/cakephp/cakephp')) { return $root; } } while ($root !== $lastRoot); throw new Exception("Cannot find the root of the application, unable to run tests"); }; $root = $findRoot(__FILE__); unset($findRoot); chdir($root); require $root . '/config/bootstrap.php';
see also
Testing stand-alone developed plugins
When developing plugins offline, this is when you really need a boot file that sets up your environment, and this is where the plugins composer.json file generated by bake is used. By default, the latter will look like
{ "name": "your-name-here/Contrast", "description": "Contrast plugin for CakePHP", "type": "cakephp-plugin", "require": { "php": ">=5.4.16", "cakephp/cakephp": "~3.0" }, "require-dev": { "phpunit/phpunit": "*" }, "autoload": { "psr-4": { "Contrast\\": "src" } }, "autoload-dev": { "psr-4": { "Contrast\\Test\\": "tests", "Cake\\Test\\": "./vendor/cakephp/cakephp/tests" } } }
The test bootstrap file created when the plugin was baked does not work out of the box, since everything he would do would try to load your plugin's config/bootstrap.php file, which by default does not even exist.
What a test bootstrap file should do depends on what the plugin does, but at least it should
- define the basic constants and configuration used by the kernel
- composer autoloader required
- CakePHP bootstrap required
- and upload / register your plugin.
For example, here is an example of tests/bootstrap.php with a copy of what the current cakephp/app application template cakephp/app , that is, it basically sets up the entire application environment (with the application that is defined in the tests/TestApp ),
// from `config/paths.php` if (!defined('DS')) { define('DS', DIRECTORY_SEPARATOR); } define('ROOT', dirname(__DIR__)); define('APP_DIR', 'test_app'); define('APP', ROOT . DS . 'tests' . DS . APP_DIR . DS); define('CONFIG', ROOT . DS . 'config' . DS); define('WWW_ROOT', APP . 'webroot' . DS); define('TESTS', ROOT . DS . 'tests' . DS); define('TMP', ROOT . DS . 'tmp' . DS); define('LOGS', TMP . 'logs' . DS); define('CACHE', TMP . 'cache' . DS); define('CAKE_CORE_INCLUDE_PATH', ROOT . DS . 'vendor' . DS . 'cakephp' . DS . 'cakephp'); define('CORE_PATH', CAKE_CORE_INCLUDE_PATH . DS); define('CAKE', CORE_PATH . 'src' . DS); // from `config/app.default.php` and `config/bootstrap.php` use Cake\Cache\Cache; use Cake\Console\ConsoleErrorHandler; use Cake\Core\App; use Cake\Core\Configure; use Cake\Core\Configure\Engine\PhpConfig; use Cake\Core\Plugin; use Cake\Database\Type; use Cake\Datasource\ConnectionManager; use Cake\Error\ErrorHandler; use Cake\Log\Log; use Cake\Mailer\Email; use Cake\Network\Request; use Cake\Routing\DispatcherFactory; use Cake\Utility\Inflector; use Cake\Utility\Security; require ROOT . DS . 'vendor' . DS . 'autoload.php'; require CORE_PATH . 'config' . DS . 'bootstrap.php'; $config = [ 'debug' => true, 'App' => [ 'namespace' => 'App', 'encoding' => env('APP_ENCODING', 'UTF-8'), 'defaultLocale' => env('APP_DEFAULT_LOCALE', 'en_US'), 'base' => false, 'dir' => 'src', 'webroot' => 'webroot', 'wwwRoot' => WWW_ROOT, 'fullBaseUrl' => false, 'imageBaseUrl' => 'img/', 'cssBaseUrl' => 'css/', 'jsBaseUrl' => 'js/', 'paths' => [ 'plugins' => [ROOT . DS . 'plugins' . DS], 'templates' => [APP . 'Template' . DS], 'locales' => [APP . 'Locale' . DS], ], ], 'Asset' => [ // 'timestamp' => true, ], 'Security' => [ 'salt' => env('SECURITY_SALT', '__SALT__'), ], 'Cache' => [ 'default' => [ 'className' => 'File', 'path' => CACHE, 'url' => env('CACHE_DEFAULT_URL', null), ], '_cake_core_' => [ 'className' => 'File', 'prefix' => 'myapp_cake_core_', 'path' => CACHE . 'persistent/', 'serialize' => true, 'duration' => '+2 minutes', 'url' => env('CACHE_CAKECORE_URL', null), ], '_cake_model_' => [ 'className' => 'File', 'prefix' => 'myapp_cake_model_', 'path' => CACHE . 'models/', 'serialize' => true, 'duration' => '+2 minutes', 'url' => env('CACHE_CAKEMODEL_URL', null), ], ], 'Error' => [ 'errorLevel' => E_ALL & ~E_DEPRECATED, 'exceptionRenderer' => 'Cake\Error\ExceptionRenderer', 'skipLog' => [], 'log' => true, 'trace' => true, ], 'EmailTransport' => [ 'default' => [ 'className' => 'Mail', // The following keys are used in SMTP transports 'host' => 'localhost', 'port' => 25, 'timeout' => 30, 'username' => 'user', 'password' => 'secret', 'client' => null, 'tls' => null, 'url' => env('EMAIL_TRANSPORT_DEFAULT_URL', null), ], ], 'Email' => [ 'default' => [ 'transport' => 'default', 'from' => ' you@localhost ', //'charset' => 'utf-8', //'headerCharset' => 'utf-8', ], ], 'Datasources' => [ 'test' => [ 'className' => 'Cake\Database\Connection', 'driver' => 'Cake\Database\Driver\Mysql', 'persistent' => false, 'host' => 'localhost', //'port' => 'non_standard_port_number', 'username' => 'my_app', 'password' => 'secret', 'database' => 'test_myapp', 'encoding' => 'utf8', 'timezone' => 'UTC', 'cacheMetadata' => true, 'quoteIdentifiers' => false, 'log' => false, //'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'], 'url' => env('DATABASE_TEST_URL', null), ], ], 'Log' => [ 'debug' => [ 'className' => 'Cake\Log\Engine\FileLog', 'path' => LOGS, 'file' => 'debug', 'levels' => ['notice', 'info', 'debug'], 'url' => env('LOG_DEBUG_URL', null), ], 'error' => [ 'className' => 'Cake\Log\Engine\FileLog', 'path' => LOGS, 'file' => 'error', 'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'], 'url' => env('LOG_ERROR_URL', null), ], ], 'Session' => [ 'defaults' => 'php', ], ]; Configure::write($config); date_default_timezone_set('UTC'); mb_internal_encoding(Configure::read('App.encoding')); ini_set('intl.default_locale', Configure::read('App.defaultLocale')); Cache::config(Configure::consume('Cache')); ConnectionManager::config(Configure::consume('Datasources')); Email::configTransport(Configure::consume('EmailTransport')); Email::config(Configure::consume('Email')); Log::config(Configure::consume('Log')); Security::salt(Configure::consume('Security.salt')); DispatcherFactory::add('Asset'); DispatcherFactory::add('Routing'); DispatcherFactory::add('ControllerFactory'); Type::build('time') ->useImmutable() ->useLocaleParser(); Type::build('date') ->useImmutable() ->useLocaleParser(); Type::build('datetime') ->useImmutable() ->useLocaleParser(); // finally load/register the plugin using a custom path Plugin::load('Contrast', ['path' => ROOT]);
In order for the classes to be autoloadable from the folder of the test application, you need to add the corresponding autoload entry to your composer.json file (and reset the autoloader again), for example:
"autoload-dev": { "psr-4": { "Contrast\\Test\\": "tests", "Contrast\\TestApp\\": "tests/test_app/src", // < here we go "Cake\\Test\\": "./vendor/cakephp/cakephp/tests" } }
So, now that the plugin has been created using bake, a customized environment and the installed dependencies of CakePHP and PHPUnit, you should be able to run your tests in the same way as with the application, i.e.
$ vendor/bin/phpunit