CakePHP comes with comprehensive testing support built-in. CakePHP comes withintegration for PHPUnit. In addition to the featuresoffered by PHPUnit, CakePHP offers some additional features to make testingeasier. This section will cover installing PHPUnit, and getting started withUnit Testing, and how you can use the extensions that CakePHP offers.

Installing PHPUnit

CakePHP uses PHPUnit as its underlying test framework. PHPUnit is the de-factostandard for unit testing in PHP. It offers a deep and powerful set of featuresfor making sure your code does what you think it does. PHPUnit can be installedthrough using either a PHAR package orComposer.

Install PHPUnit with Composer

To install PHPUnit with Composer:

  1. $ php composer.phar require --dev phpunit/phpunit:"^7.0"

This will add the dependency to the require-dev section of yourcomposer.json, and then install PHPUnit along with any dependencies.

You can now run PHPUnit using:

  1. $ vendor/bin/phpunit

Using the PHAR File

After you have downloaded the phpunit.phar file, you can use it to run yourtests:

  1. php phpunit.phar


As a convenience you can make phpunit.phar available globallyon Unix or Linux with the following:

  1. chmod +x phpunit.phar
  2. sudo mv phpunit.phar /usr/local/bin/phpunit
  3. phpunit --version

Please refer to the PHPUnit documentation for instructions regardingGlobally installing the PHPUnit PHAR on Windows.

Test Database Setup

Remember to have debug enabled in your config/app.php file before runningany tests. Before running any tests you should be sure to add a testdatasource configuration to config/app.php. This configuration is used byCakePHP for fixture tables and data:

  1. 'Datasources' => [
  2. 'test' => [
  3. 'datasource' => 'Cake\Database\Driver\Mysql',
  4. 'persistent' => false,
  5. 'host' => 'dbhost',
  6. 'username' => 'dblogin',
  7. 'password' => 'dbpassword',
  8. 'database' => 'test_database'
  9. ],
  10. ],


It’s a good idea to make the test database and your actual databasedifferent databases. This will prevent embarrassing mistakes later.

Checking the Test Setup

After installing PHPUnit and setting up your test datasource configurationyou can make sure you’re ready to write and run your own tests by running yourapplication’s tests:

  1. # For phpunit.phar
  2. $ php phpunit.phar
  4. # For Composer installed phpunit
  5. $ vendor/bin/phpunit

The above should run any tests you have, or let you know that no tests were run.To run a specific test you can supply the path to the test as a parameter toPHPUnit. For example, if you had a test case for ArticlesTable class you couldrun it with:

  1. $ vendor/bin/phpunit tests/TestCase/Model/Table/ArticlesTableTest

You should see a green bar with some additional information about the tests run,and number passed.


If you are on a Windows system you probably won’t see any colours.

Test Case Conventions

Like most things in CakePHP, test cases have some conventions. Concerningtests:

  • PHP files containing tests should be in yourtests/TestCase/[Type] directories.
  • The filenames of these files should end in Test.php insteadof just .php.
  • The classes containing tests should extend Cake\TestSuite\TestCase,Cake\TestSuite\IntegrationTestCase or \PHPUnit\Framework\TestCase.
  • Like other classnames, the test case classnames should match the filename.RouterTest.php should contain class RouterTest extends TestCase.
  • The name of any method containing a test (i.e. containing anassertion) should begin with test, as in testPublished().You can also use the @test annotation to mark methods as test methods.

Creating Your First Test Case

In the following example, we’ll create a test case for a very simple helpermethod. The helper we’re going to test will be formatting progress bar HTML.Our helper looks like:

  1. namespace App\View\Helper;
  3. use Cake\View\Helper;
  5. class ProgressHelper extends Helper
  6. {
  7. public function bar($value)
  8. {
  9. $width = round($value / 100, 2) * 100;
  10. return sprintf(
  11. '<div class="progress-container">
  12. <div class="progress-bar" style="width: %s%%"></div>
  13. </div>', $width);
  14. }
  15. }

This is a very simple example, but it will be useful to show how you can createa simple test case. After creating and saving our helper, we’ll create the testcase file in tests/TestCase/View/Helper/ProgressHelperTest.php. In that filewe’ll start with the following:

  1. namespace App\Test\TestCase\View\Helper;
  3. use App\View\Helper\ProgressHelper;
  4. use Cake\TestSuite\TestCase;
  5. use Cake\View\View;
  7. class ProgressHelperTest extends TestCase
  8. {
  9. public function setUp()
  10. {
  11. }
  13. public function testBar()
  14. {
  15. }
  16. }

We’ll flesh out this skeleton in a minute. We’ve added two methods to startwith. First is setUp(). This method is called before every test methodin a test case class. Setup methods should initialize the objects needed for thetest, and do any configuration needed. In our setup method we’ll add thefollowing:

  1. public function setUp()
  2. {
  3. parent::setUp();
  4. $View = new View();
  5. $this->Progress = new ProgressHelper($View);
  6. }

Calling the parent method is important in test cases, as TestCase::setUp()does a number things like backing up the values inCore\Configure and, storing the paths inCore\App.

Next, we’ll fill out the test method. We’ll use some assertions to ensure thatour code creates the output we expect:

  1. public function testBar()
  2. {
  3. $result = $this->Progress->bar(90);
  4. $this->assertContains('width: 90%', $result);
  5. $this->assertContains('progress-bar', $result);
  7. $result = $this->Progress->bar(33.3333333);
  8. $this->assertContains('width: 33%', $result);
  9. }

The above test is a simple one but shows the potential benefit of using testcases. We use assertContains() to ensure that our helper is returning astring that contains the content we expect. If the result did not contain theexpected content the test would fail, and we would know that our code isincorrect.

By using test cases you can describe the relationship between a set ofknown inputs and their expected output. This helps you be more confident of thecode you’re writing as you can ensure that the code you wrote fulfills theexpectations and assertions your tests make. Additionally because tests arecode, they are easy to re-run whenever you make a change. This helps preventthe creation of new bugs.


EventManager is refreshed for each test method. This means that when runningmultiple tests at once, you will lose your event listeners that wereregistered in config/bootstrap.php as the bootstrap is only executed once.

Running Tests

Once you have PHPUnit installed and some test cases written, you’ll want to runthe test cases very frequently. It’s a good idea to run tests before committingany changes to help ensure you haven’t broken anything.

By using phpunit you can run your application tests. To run yourapplication’s tests you can simply run:

  1. # composer install
  2. $ vendor/bin/phpunit
  4. # phar file
  5. php phpunit.phar

If you have cloned the CakePHP source from GitHuband wish to run CakePHP’s unit-tests don’t forget to execute the following Composercommand prior to running phpunit so that any dependencies are installed:

  1. $ composer install

From your application’s root directory. To run tests for a plugin that is partof your application source, first cd into the plugin directory, then usephpunit command that matches how you installed phpunit:

  1. cd plugins
  3. # Using composer installed phpunit
  4. ../vendor/bin/phpunit
  6. # Using phar file
  7. php ../phpunit.phar

To run tests on a standalone plugin, you should first install the project ina separate directory and install its dependencies:

  1. git clone git://github.com/cakephp/debug_kit.git
  2. cd debug_kit
  3. php ~/composer.phar install
  4. php ~/phpunit.phar

Filtering Test Cases

When you have larger test cases, you will often want to run a subset of the testmethods when you are trying to work on a single failing case. With theCLI runner you can use an option to filter test methods:

  1. $ phpunit --filter testSave tests/TestCase/Model/Table/ArticlesTableTest

The filter parameter is used as a case-sensitive regular expression forfiltering which test methods to run.

Generating Code Coverage

You can generate code coverage reports from the command line using PHPUnit’sbuilt-in code coverage tools. PHPUnit will generate a set of static HTML filescontaining the coverage results. You can generate coverage for a test case bydoing the following:

  1. $ phpunit --coverage-html webroot/coverage tests/TestCase/Model/Table/ArticlesTableTest

This will put the coverage results in your application’s webroot directory. Youshould be able to view the results by going tohttp://localhost/your_app/coverage.

You can also use phpdbg to generate coverage instead of xdebug.phpdbg is generally faster at generating coverage:

  1. $ phpdbg -qrr phpunit --coverage-html webroot/coverage tests/TestCase/Model/Table/ArticlesTableTest

Combining Test Suites for Plugins

Often times your application will be composed of several plugins. In thesesituations it can be pretty tedious to run tests for each plugin. You can makerunning tests for each of the plugins that compose your application by addingadditional <testsuite> sections to your application’s phpunit.xml.distfile:

  1. <testsuites>
  2. <testsuite name="app">
  3. <directory>./tests/TestCase/</directory>
  4. </testsuite>
  6. <!-- Add your plugin suites -->
  7. <testsuite name="forum">
  8. <directory>./plugins/Forum/tests/TestCase/</directory>
  9. </testsuite>
  10. </testsuites>

Any additional test suites added to the <testsuites> element willautomatically be run when you use phpunit.

If you are using <testsuites> to use fixtures from plugins that you haveinstalled with composer, the plugin’s composer.json file should add thefixture namespace to the autoload section. Example:

  1. "autoload-dev": {
  2. "psr-4": {
  3. "PluginName\\Test\\Fixture\\": "tests/Fixture/"
  4. }
  5. },

Test Case Lifecycle Callbacks

Test cases have a number of lifecycle callbacks you can use when doing testing:

  • setUp is called before every test method. Should be used to create theobjects that are going to be tested, and initialize any data for the test.Always remember to call parent::setUp()
  • tearDown is called after every test method. Should be used to cleanup afterthe test is complete. Always remember to call parent::tearDown().
  • setupBeforeClass is called once before test methods in a case are started.This method must be static.
  • tearDownAfterClass is called once after test methods in a case are started.This method must be static.


When testing code that depends on models and the database, one can usefixtures as a way to generate temporary data tables loaded with sample datathat can be used by the test. The benefit of using fixtures is that your testhas no chance of disrupting live application data. In addition, you can begintesting your code prior to actually developing live content for an application.

CakePHP uses the connection named test in your config/app.phpconfiguration file. If this connection is not usable, an exception will beraised and you will not be able to use database fixtures.

CakePHP performs the following during the course of a fixture basedtest case:

  • Creates tables for each of the fixtures needed.
  • Populates tables with data, if data is provided in fixture.
  • Runs test methods.
  • Empties the fixture tables.
  • Removes fixture tables from database.

Test Connections

By default CakePHP will alias each connection in your application. Eachconnection defined in your application’s bootstrap that does not start withtest will have a test prefixed alias created. Aliasing connectionsensures, you don’t accidentally use the wrong connection in test cases.Connection aliasing is transparent to the rest of your application. For exampleif you use the ‘default’ connection, instead you will get the testconnection in test cases. If you use the ‘replica’ connection, the test suitewill attempt to use ‘test_replica’.

PHPUnit Configuration

Before you can use fixtures you should double check that your phpunit.xmlcontains the fixture listener:

  1. <!-- in phpunit.xml -->
  2. <!-- Setup a listener for fixtures -->
  3. <listeners>
  4. <listener
  5. class="\Cake\TestSuite\Fixture\FixtureInjector">
  6. <arguments>
  7. <object class="\Cake\TestSuite\Fixture\FixtureManager" />
  8. </arguments>
  9. </listener>
  10. </listeners>

The listener is included in your application and plugins generated by bakeby default.

Creating Fixtures

When creating a fixture you will mainly define two things: how the table iscreated (which fields are part of the table), and which records will beinitially populated to the table. Let’s create our first fixture, that will beused to test our own Article model. Create a file named ArticlesFixture.phpin your tests/Fixture directory, with the following content:

  1. namespace App\Test\Fixture;
  3. use Cake\TestSuite\Fixture\TestFixture;
  5. class ArticlesFixture extends TestFixture
  6. {
  7. // Optional. Set this property to load fixtures to a different test datasource
  8. public $connection = 'test';
  10. public $fields = [
  11. 'id' => ['type' => 'integer'],
  12. 'title' => ['type' => 'string', 'length' => 255, 'null' => false],
  13. 'body' => 'text',
  14. 'published' => ['type' => 'integer', 'default' => '0', 'null' => false],
  15. 'created' => 'datetime',
  16. 'modified' => 'datetime',
  17. '_constraints' => [
  18. 'primary' => ['type' => 'primary', 'columns' => ['id']]
  19. ]
  20. ];
  21. public $records = [
  22. [
  23. 'title' => 'First Article',
  24. 'body' => 'First Article Body',
  25. 'published' => '1',
  26. 'created' => '2007-03-18 10:39:23',
  27. 'modified' => '2007-03-18 10:41:31'
  28. ],
  29. [
  30. 'title' => 'Second Article',
  31. 'body' => 'Second Article Body',
  32. 'published' => '1',
  33. 'created' => '2007-03-18 10:41:23',
  34. 'modified' => '2007-03-18 10:43:31'
  35. ],
  36. [
  37. 'title' => 'Third Article',
  38. 'body' => 'Third Article Body',
  39. 'published' => '1',
  40. 'created' => '2007-03-18 10:43:23',
  41. 'modified' => '2007-03-18 10:45:31'
  42. ]
  43. ];
  44. }


It is recommended to not manually add values to auto incremental columns,as it interferes with the sequence generation in PostgreSQL and SQLServer.

The $connection property defines the datasource of which the fixture willuse. If your application uses multiple datasources, you should make thefixtures match the model’s datasources but prefixed with test_.For example if your model uses the mydb datasource, your fixture should usethe test_mydb datasource. If the test_mydb connection doesn’t exist,your models will use the default test datasource. Fixture datasources mustbe prefixed with test to reduce the possibility of accidentally truncatingall your application’s data when running tests.

We use $fields to specify which fields will be part of this table, and howthey are defined. The format used to define these fields is the same used withCake\Database\Schema\Table. The keys available for tabledefinition are:

  • type
  • CakePHP internal data type. Currently supported:

    • string: maps to VARCHAR
    • char: maps to CHAR
    • uuid: maps to UUID
    • text: maps to TEXT
    • integer: maps to INT
    • biginteger: maps to BIGINTEGER
    • decimal: maps to DECIMAL
    • float: maps to FLOAT
    • datetime: maps to DATETIME
    • datetimefractional: maps to DATETIME(6) or TIMESTAMP
    • timestamp: maps to TIMESTAMP
    • timestampfractional: maps to TIMESTAMP(6) or TIMESTAMP
    • timestamp: maps to TIMESTAMP
    • time: maps to TIME
    • date: maps to DATE
    • binary: maps to BLOB
  • length
  • Set to the specific length the field should take.
  • precision
  • Set the number of decimal places used on float & decimal fields.
  • null
  • Set to either true (to allow NULLs) or false (to disallow NULLs).
  • default
  • Default value the field takes.

We can define a set of records that will be populated after the fixture table iscreated. The format is fairly straight forward, $records is an array ofrecords. Each item in $records should be a single row. Inside each row,should be an associative array of the columns and values for the row. Just keepin mind that each record in the $records array must have a key for everyfield specified in the $fields array. If a field for a particular recordneeds to have a null value, just specify the value of that key as null.

Dynamic Data and Fixtures

Since records for a fixture are declared as a class property, you cannot usefunctions or other dynamic data to define fixtures. To solve this problem, youcan define $records in the init() function of your fixture. For exampleif you wanted all the created and modified timestamps to reflect today’s dateyou could do the following:

  1. namespace App\Test\Fixture;
  3. use Cake\TestSuite\Fixture\TestFixture;
  5. class ArticlesFixture extends TestFixture
  6. {
  7. public $fields = [
  8. 'id' => ['type' => 'integer'],
  9. 'title' => ['type' => 'string', 'length' => 255, 'null' => false],
  10. 'body' => 'text',
  11. 'published' => ['type' => 'integer', 'default' => '0', 'null' => false],
  12. 'created' => 'datetime',
  13. 'modified' => 'datetime',
  14. '_constraints' => [
  15. 'primary' => ['type' => 'primary', 'columns' => ['id']],
  16. ]
  17. ];
  19. public function init()
  20. {
  21. $this->records = [
  22. [
  23. 'title' => 'First Article',
  24. 'body' => 'First Article Body',
  25. 'published' => '1',
  26. 'created' => date('Y-m-d H:i:s'),
  27. 'modified' => date('Y-m-d H:i:s'),
  28. ],
  29. ];
  30. parent::init();
  31. }
  32. }

When overriding init() remember to always call parent::init().

Importing Table Information

Defining the schema in fixture files can be really handy when creating pluginsor libraries or if you are creating an application that needs to be portablebetween database vendors. Redefining the schema in fixtures can become difficultto maintain in larger applications. Because of this CakePHP provides the abilityto import the schema from an existing connection and use the reflected tabledefinition to create the table definition used in the test suite.

Let’s start with an example. Assuming you have a table named articles availablein your application, change the example fixture given in the previous section(tests/Fixture/ArticlesFixture.php) to:

  1. class ArticlesFixture extends TestFixture
  2. {
  3. public $import = ['table' => 'articles'];
  4. }

If you want to use a different connection use:

  1. class ArticlesFixture extends TestFixture
  2. {
  3. public $import = ['table' => 'articles', 'connection' => 'other'];
  4. }

Usually, you have a Table class along with your fixture, as well. You can alsouse that to retrieve the table name:

  1. class ArticlesFixture extends TestFixture
  2. {
  3. public $import = ['model' => 'Articles'];
  4. }

Since this uses TableRegistry::getTableLocator()->get(), it also supports plugin syntax.

You can naturally import your table definition from an existing model/table, buthave your records defined directly on the fixture as it was shown on previoussection. For example:

  1. class ArticlesFixture extends TestFixture
  2. {
  3. public $import = ['table' => 'articles'];
  4. public $records = [
  5. [
  6. 'title' => 'First Article',
  7. 'body' => 'First Article Body',
  8. 'published' => '1',
  9. 'created' => '2007-03-18 10:39:23',
  10. 'modified' => '2007-03-18 10:41:31'
  11. ],
  12. [
  13. 'title' => 'Second Article',
  14. 'body' => 'Second Article Body',
  15. 'published' => '1',
  16. 'created' => '2007-03-18 10:41:23',
  17. 'modified' => '2007-03-18 10:43:31'
  18. ],
  19. [
  20. 'title' => 'Third Article',
  21. 'body' => 'Third Article Body',
  22. 'published' => '1',
  23. 'created' => '2007-03-18 10:43:23',
  24. 'modified' => '2007-03-18 10:45:31'
  25. ]
  26. ];
  27. }

Finally, it’s possible to not load/create any schema in a fixture. This is useful if youalready have a test database setup with all the empty tables created. Bydefining neither $fields nor $import, a fixture will only insert itsrecords and truncate the records on each test method.

Loading Fixtures in your Test Cases

After you’ve created your fixtures, you’ll want to use them in your test cases.In each test case you should load the fixtures you will need. You should load afixture for every model that will have a query run against it. To load fixturesyou define the $fixtures property in your model:

  1. class ArticlesTest extends TestCase
  2. {
  3. public $fixtures = ['app.Articles', 'app.Comments'];
  4. }


You can also override TestCase::getFixtures() instead of definingthe $fixtures property:

  1. public function getFixtures()
  2. {
  3. return ['app.Articles', 'app.Comments'];
  4. }

The above will load the Article and Comment fixtures from the application’sFixture directory. You can also load fixtures from CakePHP core, or plugins:

  1. class ArticlesTest extends TestCase
  2. {
  3. public $fixtures = [
  4. 'plugin.DebugKit.Articles',
  5. 'plugin.MyVendorName/MyPlugin.Messages',
  6. 'core.Comments'
  7. ];
  8. }

Using the core prefix will load fixtures from CakePHP, and using a pluginname as the prefix, will load the fixture from the named plugin.

You can control when your fixtures are loaded by settingCake\TestSuite\TestCase::$autoFixtures to false and later loadthem using Cake\TestSuite\TestCase::loadFixtures():

  1. class ArticlesTest extends TestCase
  2. {
  3. public $fixtures = ['app.Articles', 'app.Comments'];
  4. public $autoFixtures = false;
  6. public function testMyFunction()
  7. {
  8. $this->loadFixtures('Articles', 'Comments');
  9. }
  10. }

You can load fixtures in subdirectories. Using multiple directories can make iteasier to organize your fixtures if you have a larger application. To loadfixtures in subdirectories, simply include the subdirectory name in the fixturename:

  1. class ArticlesTest extends CakeTestCase
  2. {
  3. public $fixtures = ['app.Blog/Articles', 'app.Blog/Comments'];
  4. }

In the above example, both fixtures would be loaded fromtests/Fixture/Blog/.

Testing Table Classes

Let’s say we already have our Articles Table class defined insrc/Model/Table/ArticlesTable.php, and it looks like:

  1. namespace App\Model\Table;
  3. use Cake\ORM\Table;
  4. use Cake\ORM\Query;
  6. class ArticlesTable extends Table
  7. {
  8. public function findPublished(Query $query, array $options)
  9. {
  10. $query->where([
  11. $this->alias() . '.published' => 1
  12. ]);
  13. return $query;
  14. }
  15. }

We now want to set up a test that will test this table class. Let’s now createa file named ArticlesTableTest.php in your tests/TestCase/Model/Table directory,with the following contents:

  1. namespace App\Test\TestCase\Model\Table;
  3. use App\Model\Table\ArticlesTable;
  4. use Cake\ORM\TableRegistry;
  5. use Cake\TestSuite\TestCase;
  7. class ArticlesTableTest extends TestCase
  8. {
  9. public $fixtures = ['app.Articles'];
  10. }

In our test cases’ variable $fixtures we define the set of fixtures thatwe’ll use. You should remember to include all the fixtures that will havequeries run against them.

Creating a Test Method

Let’s now add a method to test the function published() in the Articlestable. Edit the file tests/TestCase/Model/Table/ArticlesTableTest.php so itnow looks like this:

  1. namespace App\Test\TestCase\Model\Table;
  3. use App\Model\Table\ArticlesTable;
  4. use Cake\ORM\TableRegistry;
  5. use Cake\TestSuite\TestCase;
  7. class ArticlesTableTest extends TestCase
  8. {
  9. public $fixtures = ['app.Articles'];
  11. public function setUp()
  12. {
  13. parent::setUp();
  14. $this->Articles = TableRegistry::getTableLocator()->get('Articles');
  15. }
  17. public function testFindPublished()
  18. {
  19. $query = $this->Articles->find('published');
  20. $this->assertInstanceOf('Cake\ORM\Query', $query);
  21. $result = $query->enableHydration(false)->toArray();
  22. $expected = [
  23. ['id' => 1, 'title' => 'First Article'],
  24. ['id' => 2, 'title' => 'Second Article'],
  25. ['id' => 3, 'title' => 'Third Article']
  26. ];
  28. $this->assertEquals($expected, $result);
  29. }
  30. }

You can see we have added a method called testFindPublished(). We start bycreating an instance of our ArticlesTable class, and then run ourfind('published') method. In $expected we set what we expect should bethe proper result (that we know since we have defined which records areinitially populated to the article table.) We test that the result equals ourexpectation by using the assertEquals() method. See the Running Testssection for more information on how to run your test case.

Mocking Model Methods

There will be times you’ll want to mock methods on models when testing them. Youshould use getMockForModel to create testing mocks of table classes. Itavoids issues with reflected properties that normal mocks have:

  1. public function testSendingEmails()
  2. {
  3. $model = $this->getMockForModel('EmailVerification', ['send']);
  4. $model->expects($this->once())
  5. ->method('send')
  6. ->will($this->returnValue(true));
  8. $model->verifyEmail('test@example.com');
  9. }

In your tearDown() method be sure to remove the mock with:

  1. TableRegistry::clear();

Controller Integration Testing

While you can test controller classes in a similar fashion to Helpers, Models,and Components, CakePHP offers a specialized IntegrationTestTrait trait.Using this trait in your controller test cases allows you totest controllers from a high level.

If you are unfamiliar with integration testing, it is a testing approach thatmakes it easy to test multiple units in concert. The integration testingfeatures in CakePHP simulate an HTTP request being handled by your application.For example, testing your controller will also exercise any components, modelsand helpers that would be involved in handling a given request. This gives you amore high level test of your application and all its working parts.

Say you have a typical ArticlesController, and its corresponding model. Thecontroller code looks like:

  1. namespace App\Controller;
  3. use App\Controller\AppController;
  5. class ArticlesController extends AppController
  6. {
  7. public $helpers = ['Form', 'Html'];
  9. public function index($short = null)
  10. {
  11. if ($this->request->is('post')) {
  12. $article = $this->Articles->newEntity($this->request->getData());
  13. if ($this->Articles->save($article)) {
  14. // Redirect as per PRG pattern
  15. return $this->redirect(['action' => 'index']);
  16. }
  17. }
  18. if (!empty($short)) {
  19. $result = $this->Articles->find('all', [
  20. 'fields' => ['id', 'title']
  21. ]);
  22. } else {
  23. $result = $this->Articles->find();
  24. }
  26. $this->set([
  27. 'title' => 'Articles',
  28. 'articles' => $result
  29. ]);
  30. }
  31. }

Create a file named ArticlesControllerTest.php in yourtests/TestCase/Controller directory and put the following inside:

  1. namespace App\Test\TestCase\Controller;
  3. use Cake\ORM\TableRegistry;
  4. use Cake\TestSuite\IntegrationTestTrait;
  5. use Cake\TestSuite\TestCase;
  7. class ArticlesControllerTest extends TestCase
  8. {
  9. use IntegrationTestTrait;
  11. public $fixtures = ['app.Articles'];
  13. public function testIndex()
  14. {
  15. $this->get('/articles');
  17. $this->assertResponseOk();
  18. // More asserts.
  19. }
  21. public function testIndexQueryData()
  22. {
  23. $this->get('/articles?page=1');
  25. $this->assertResponseOk();
  26. // More asserts.
  27. }
  29. public function testIndexShort()
  30. {
  31. $this->get('/articles/index/short');
  33. $this->assertResponseOk();
  34. $this->assertResponseContains('Articles');
  35. // More asserts.
  36. }
  38. public function testIndexPostData()
  39. {
  40. $data = [
  41. 'user_id' => 1,
  42. 'published' => 1,
  43. 'slug' => 'new-article',
  44. 'title' => 'New Article',
  45. 'body' => 'New Body'
  46. ];
  47. $this->post('/articles', $data);
  49. $this->assertResponseSuccess();
  50. $articles = TableRegistry::getTableLocator()->get('Articles');
  51. $query = $articles->find()->where(['title' => $data['title']]);
  52. $this->assertEquals(1, $query->count());
  53. }
  54. }

This example shows a few of the request sending methods and a few of theassertions that IntegrationTestTrait provides. Before you can do anyassertions you’ll need to dispatch a request. You can use one of the followingmethods to send a request:

  • get() Sends a GET request.
  • post() Sends a POST request.
  • put() Sends a PUT request.
  • delete() Sends a DELETE request.
  • patch() Sends a PATCH request.
  • options() Sends an OPTIONS request.
  • head() Sends a HEAD request.

All of the methods except get() and delete() accept a second parameterthat allows you to send a request body. After dispatching a request you can usethe various assertions provided by IntegrationTestTrait or PHPUnit toensure your request had the correct side-effects.

Setting up the Request

The IntegrationTestTrait trait comes with a number of helpers to make it easyto configure the requests you will send to your application under test:

  1. // Set cookies
  2. $this->cookie('name', 'Uncle Bob');
  4. // Set session data
  5. $this->session(['Auth.User.id' => 1]);
  7. // Configure headers
  8. $this->configRequest([
  9. 'headers' => ['Accept' => 'application/json']
  10. ]);

The state set by these helper methods is reset in the tearDown() method.

Testing Actions That Require Authentication

If you are using AuthComponent you will need to stub out the session datathat AuthComponent uses to validate a user’s identity. You can use helpermethods in IntegrationTestTrait to do this. Assuming you had anArticlesController that contained an add method, and that add methodrequired authentication, you could write the following tests:

  1. public function testAddUnauthenticatedFails()
  2. {
  3. // No session data set.
  4. $this->get('/articles/add');
  6. $this->assertRedirect(['controller' => 'Users', 'action' => 'login']);
  7. }
  9. public function testAddAuthenticated()
  10. {
  11. // Set session data
  12. $this->session([
  13. 'Auth' => [
  14. 'User' => [
  15. 'id' => 1,
  16. 'username' => 'testing',
  17. // other keys.
  18. ]
  19. ]
  20. ]);
  21. $this->get('/articles/add');
  23. $this->assertResponseOk();
  24. // Other assertions.
  25. }

Testing Stateless Authentication and APIs

To test APIs that use stateless authentication, such as Basic authentication,you can configure the request to inject environment conditions or headers thatsimulate actual authentication request headers.

When testing Basic or Digest Authentication, you can add the environmentvariables that PHP createsautomatically. These environment variables used in the authentication adapteroutlined in Using Basic Authentication:

  1. public function testBasicAuthentication()
  2. {
  3. $this->configRequest([
  4. 'environment' => [
  5. 'PHP_AUTH_USER' => 'username',
  6. 'PHP_AUTH_PW' => 'password',
  7. ]
  8. ]);
  10. $this->get('/api/posts');
  11. $this->assertResponseOk();
  12. }

If you are testing other forms of authentication, such as OAuth2, you can setthe Authorization header directly:

  1. public function testOauthToken()
  2. {
  3. $this->configRequest([
  4. 'headers' => [
  5. 'authorization' => 'Bearer: oauth-token'
  6. ]
  7. ]);
  9. $this->get('/api/posts');
  10. $this->assertResponseOk();
  11. }

The headers key in configRequest() can be used to configure any additionalHTTP headers needed for an action.

Testing Actions Protected by CsrfComponent or SecurityComponent

When testing actions protected by either SecurityComponent or CsrfComponent youcan enable automatic token generation to ensure your tests won’t fail due totoken mismatches:

  1. public function testAdd()
  2. {
  3. $this->enableCsrfToken();
  4. $this->enableSecurityToken();
  5. $this->post('/posts/add', ['title' => 'Exciting news!']);
  6. }

It is also important to enable debug in tests that use tokens to prevent theSecurityComponent from thinking the debug token is being used in a non-debugenvironment. When testing with other methods like requireSecure() youcan use configRequest() to set the correct environment variables:

  1. // Fake out SSL connections.
  2. $this->configRequest([
  3. 'environment' => ['HTTPS' => 'on']
  4. ]);

If your action requires unlocked fields you can declare them withsetUnlockedFields():


Integration Testing PSR-7 Middleware

Integration testing can also be used to test your entire PSR-7 application andMiddleware. By default IntegrationTestTrait willauto-detect the presence of an App\Application class and automaticallyenable integration testing of your Application. You can toggle this behaviorwith the useHttpServer() method:

public function setUp()
    // Enable PSR-7 integration testing.

    // Disable PSR-7 integration testing.

You can customize the application class name used, and the constructorarguments, by using the configApplication() method:

public function setUp()
    $this->configApplication('App\App', [CONFIG]);

After enabling the PSR-7 mode, and possibly configuring your application class,you can use the remaining IntegrationTestTrait features as normal.

You should also take care to try and use Application::bootstrap() to loadany plugins containing events/routes. Doing so will ensure that yourevents/routes are connected for each test case. Alternatively if you wish toload plugins manually in a test you can use the loadPlugins() method.

Testing with Encrypted Cookies

If you use the encrypted-cookie-middleware: in yourapplication, there are helper methods for setting encrypted cookies in yourtest cases:

// Set a cookie using AES and the default key.
$this->cookieEncrypted('my_cookie', 'Some secret values');

// Assume this action modifies the cookie.

$this->assertCookieEncrypted('An updated value', 'my_cookie');

Testing Flash Messages

If you want to assert the presence of flash messages in the session and not therendered HTML, you can use enableRetainFlashMessages() in your tests toretain flash messages in the session so you can write assertions:

// Enable retention of flash messages instead of consuming them.

$this->assertSession('That bookmark does not exist', 'Flash.flash.0.message');

// Assert a flash message in the 'flash' key.
$this->assertFlashMessage('Bookmark deleted', 'flash');

// Assert the second flash message, also  in the 'flash' key.
$this->assertFlashMessageAt(1, 'Bookmark really deleted');

// Assert a flash message in the 'auth' key at the first position
$this->assertFlashMessageAt(0, 'You are not allowed to enter this dungeon!', 'auth');

// Assert a flash messages uses the error element

// Assert the second flash message element
$this->assertFlashElementAt(1, 'Flash/error');

Testing a JSON Responding Controller

JSON is a friendly and common format to use when building a web service.Testing the endpoints of your web service is very simple with CakePHP. Let usbegin with a simple example controller that responds in JSON:

class MarkersController extends AppController
    public function initialize(): void

    public function view($id)
        $marker = $this->Markers->get($id);
        $this->set('marker', $marker);
        $this->viewBuilder()->setOption('serialize', ['marker']);

Now we create the file tests/TestCase/Controller/MarkersControllerTest.phpand make sure our web service is returning the proper response:

class MarkersControllerTest extends IntegrationTestCase
    public function testGet()
            'headers' => ['Accept' => 'application/json']
        $result = $this->get('/markers/view/1.json');

        // Check that the response was a 200

        $expected = [
            ['id' => 1, 'lng' => 66, 'lat' => 45],
        $expected = json_encode($expected, JSON_PRETTY_PRINT);
        $this->assertEquals($expected, (string)$this->_response->getBody());

We use the JSON_PRETTY_PRINT option as CakePHP’s built in JsonView will usethat option when debug is enabled.

Disabling Error Handling Middleware in Tests

When debugging tests that are failing because your application is encounteringerrors it can be helpful to temporarily disable the error handling middleware toallow the underlying error to bubble up. You can usedisableErrorHandlerMiddleware() to do this:

public function testGetMissing()

In the above example, the test would fail and the underlying exception messageand stack trace would be displayed instead of the rendered error page beingchecked.

Assertion methods

The IntegrationTestTrait trait provides a number of assertion methods thatmake testing responses much simpler. Some examples are:

// Check for a 2xx response code

// Check for a 2xx/3xx response code

// Check for a 4xx response code

// Check for a 5xx response code

// Check for a specific response code, e.g. 200

// Check the Location header
$this->assertRedirect(['controller' => 'Articles', 'action' => 'index']);

// Check that no Location header has been set

// Check a part of the Location header

// Assert location header does not contain

// Assert not empty response content

// Assert empty response content

// Assert response content

// Assert response content doesn't equal

// Assert partial response content
$this->assertResponseContains('You won!');
$this->assertResponseNotContains('You lost!');

// Assert file sent back

// Assert layout

// Assert which template was rendered (if any)

// Assert data in the session
$this->assertSession(1, 'Auth.User.id');

// Assert response header.
$this->assertHeader('Content-Type', 'application/json');
$this->assertHeaderContains('Content-Type', 'html');

// Assert content-type header doesn't contain xml
$this->assertHeaderNotContains('Content-Type', 'xml');

// Assert view variables
$user =  $this->viewVariable('user');
$this->assertEquals('jose', $user->username);

// Assert cookies in the response
$this->assertCookie('1', 'thingid');

// Check the content type

In addition to the above assertion methods, you can also use all of theassertions in TestSuite and thosefound in PHPUnit.

Comparing test results to a file

For some types of test, it may be easier to compare the result of a test to thecontents of a file - for example, when testing the rendered output of a view.The StringCompareTrait adds a simple assert method for this purpose.

Usage involves using the trait, setting the comparison base path and callingassertSameAsFile:

use Cake\TestSuite\StringCompareTrait;
use Cake\TestSuite\TestCase;

class SomeTest extends TestCase
    use StringCompareTrait;

    public function setUp()
        $this->_compareBasePath = APP . 'tests' . DS . 'comparisons' . DS;

    public function testExample()
        $result = ...;
        $this->assertSameAsFile('example.php', $result);

The above example will compare $result to the contents of the fileAPP/tests/comparisons/example.php.

A mechanism is provided to write/update test files, by setting the environmentvariable UPDATE_TEST_COMPARISON_FILES, which will create and/or update testcomparison files as they are referenced:

Tests: 6, Assertions: 7, Failures: 1

OK (6 tests, 7 assertions)

git status
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#   modified:   tests/comparisons/example.php

Console Integration Testing

See Testing Commands for information on testing shells andcommands.

Testing Views

Generally most applications will not directly test their HTML code. Doing so isoften results in fragile, difficult to maintain test suites that are prone tobreaking. When writing functional tests using IntegrationTestTraityou can inspect the rendered view content by setting the return option to‘view’. While it is possible to test view content using IntegrationTestTrait,a more robust and maintainable integration/view testing can be accomplishedusing tools like Selenium webdriver.

Testing Components

Let’s pretend we have a component called PagematronComponent in our application.This component helps us set the pagination limit value across all thecontrollers that use it. Here is our example component located insrc/Controller/Component/PagematronComponent.php:

class PagematronComponent extends Component
    public $controller = null;

    public function setController($controller)
        $this->controller = $controller;
        // Make sure the controller is using pagination
        if (!isset($this->controller->paginate)) {
            $this->controller->paginate = [];

    public function startup(EventInterface $event)

    public function adjust($length = 'short')
        switch ($length) {
            case 'long':
                $this->controller->paginate['limit'] = 100;
            case 'medium':
                $this->controller->paginate['limit'] = 50;
                $this->controller->paginate['limit'] = 20;

Now we can write tests to ensure our paginate limit parameter is being setcorrectly by the adjust() method in our component. We create the filetests/TestCase/Controller/Component/PagematronComponentTest.php:

namespace App\Test\TestCase\Controller\Component;

use App\Controller\Component\PagematronComponent;
use Cake\Controller\Controller;
use Cake\Controller\ComponentRegistry;
use Cake\Event\Event;
use Cake\Http\ServerRequest;
use Cake\Http\Response;
use Cake\TestSuite\TestCase;

class PagematronComponentTest extends TestCase
    protected $component;
    protected $controller;

    public function setUp()
        // Setup our component and fake test controller
        $request = new ServerRequest();
        $response = new Response();
        $this->controller = $this->getMockBuilder('Cake\Controller\Controller')
            ->setConstructorArgs([$request, $response])
        $registry = new ComponentRegistry($this->controller);
        $this->component = new PagematronComponent($registry);
        $event = new Event('Controller.startup', $this->controller);

    public function testAdjust()
        // Test our adjust method with different parameter settings
        $this->assertEquals(20, $this->controller->paginate['limit']);

        $this->assertEquals(50, $this->controller->paginate['limit']);

        $this->assertEquals(100, $this->controller->paginate['limit']);

    public function tearDown()
        // Clean up after we're done
        unset($this->component, $this->controller);

Testing Helpers

Since a decent amount of logic resides in Helper classes, it’simportant to make sure those classes are covered by test cases.

First we create an example helper to test. The CurrencyRendererHelper willhelp us display currencies in our views and for simplicity only has one methodusd():

// src/View/Helper/CurrencyRendererHelper.php
namespace App\View\Helper;

use Cake\View\Helper;

class CurrencyRendererHelper extends Helper
    public function usd($amount)
        return 'USD ' . number_format($amount, 2, '.', ',');

Here we set the decimal places to 2, decimal separator to dot, thousandsseparator to comma, and prefix the formatted number with ‘USD’ string.

Now we create our tests:

// tests/TestCase/View/Helper/CurrencyRendererHelperTest.php

namespace App\Test\TestCase\View\Helper;

use App\View\Helper\CurrencyRendererHelper;
use Cake\TestSuite\TestCase;
use Cake\View\View;

class CurrencyRendererHelperTest extends TestCase
    public $helper = null;

    // Here we instantiate our helper
    public function setUp()
        $View = new View();
        $this->helper = new CurrencyRendererHelper($View);

    // Testing the usd() function
    public function testUsd()
        $this->assertEquals('USD 5.30', $this->helper->usd(5.30));

        // We should always have 2 decimal digits
        $this->assertEquals('USD 1.00', $this->helper->usd(1));
        $this->assertEquals('USD 2.05', $this->helper->usd(2.05));

        // Testing the thousands separator
          'USD 12,000.70',

Here, we call usd() with different parameters and tell the test suite tocheck if the returned values are equal to what is expected.

Save this and execute the test. You should see a green bar and messagingindicating 1 pass and 4 assertions.

When you are testing a Helper which uses other helpers, be sure to mock theView clases loadHelpers method.

Testing Events

The Events System is a great way to decouple your applicationcode, but sometimes when testing, you tend to test the results of events in thetest cases that execute those events. This is an additional form of couplingthat can be removed by using assertEventFired and assertEventFiredWithinstead.

Expanding on the Orders example, say we have the following tables:

class OrdersTable extends Table
    public function place($order)
        if ($this->save($order)) {
            // moved cart removal to CartsTable
            $event = new Event('Model.Order.afterPlace', $this, [
                'order' => $order
            return true;
        return false;

class CartsTable extends Table
    public function implementedEvents()
        return [
            'Model.Order.afterPlace' => 'removeFromCart'

    public function removeFromCart(EventInterface $event)
        $order = $event->getData('order');


To assert that events are fired, you must first enableTracking Events on the event manager you wish to assert against.

To test the OrdersTable above, we enable tracking in setUp() then assertthat the event was fired, and assert that the $order entity was passed inthe event data:

namespace App\Test\TestCase\Model\Table;

use App\Model\Table\OrdersTable;
use Cake\Event\EventList;
use Cake\ORM\TableRegistry;
use Cake\TestSuite\TestCase;

class OrdersTableTest extends TestCase
    public $fixtures = ['app.Orders'];

    public function setUp()
        $this->Orders = TableRegistry::getTableLocator()->get('Orders');
        // enable event tracking
        $this->Orders->getEventManager()->setEventList(new EventList());

    public function testPlace()
        $order = new Order([
            'user_id' => 1,
            'item' => 'Cake',
            'quantity' => 42,


        $this->assertEventFired('Model.Order.afterPlace', $this->Orders->getEventManager());
        $this->assertEventFiredWith('Model.Order.afterPlace', 'order', $order, $this->Orders->getEventManager());

By default, the global EventManager is used for assertions, so testingglobal events does not require passing the event manager:

$this->assertEventFiredWith('My.Global.Event', 'user', 1);

Testing Email

See Testing Mailer for information on testing email.

Creating Test Suites

If you want several of your tests to run at the same time, you can create a testsuite. A test suite is composed of several test cases. You can either createtest suites in your application’s phpunit.xml file. A simple examplewould be:

  <testsuite name="Models">

Creating Tests for Plugins

Tests for plugins are created in their own directory inside the pluginsfolder.


They work just like normal tests but you have to remember to use the namingconventions for plugins when importing classes. This is an example of a testcasefor the BlogPost model from the plugins chapter of this manual. A differencefrom other tests is in the first line where ‘Blog.BlogPost’ is imported. Youalso need to prefix your plugin fixtures with plugin.Blog.BlogPosts:

namespace Blog\Test\TestCase\Model\Table;

use Blog\Model\Table\BlogPostsTable;
use Cake\TestSuite\TestCase;

class BlogPostsTableTest extends TestCase
    // Plugin fixtures located in /plugins/Blog/tests/Fixture/
    public $fixtures = ['plugin.Blog.BlogPosts'];

    public function testSomething()
        // Test something.

If you want to use plugin fixtures in the app tests you canreference them using plugin.pluginName.fixtureName syntax in the$fixtures array. Additionally if you use vendor plugin name or fixturedirectories you can use the following: plugin.vendorName/pluginName.folderName/fixtureName.

Before you can use fixtures you should ensure you have the fixturelistener configured in your phpunit.xmlfile. You should also ensure that your fixtures are loadable. Ensure thefollowing is present in your composer.json file:

"autoload-dev": {
    "psr-4": {
        "MyPlugin\\Test\\": "plugins/MyPlugin/tests/"


Remember to run composer.phar dumpautoload when adding new autoloadmappings.

Generating Tests with Bake

If you use bake togenerate scaffolding, it will also generate test stubs. If you need tore-generate test case skeletons, or if you want to generate test skeletons forcode you wrote, you can use bake:

bin/cake bake test <type> <name>

<type> should be one of:

  • Entity
  • Table
  • Controller
  • Component
  • Behavior
  • Helper
  • Shell
  • Task
  • ShellHelper
  • Cell
  • Form
  • Mailer
  • CommandWhile <name> should be the name of the object you want to bake a testskeleton for.

Integration with Jenkins

Jenkins is a continuous integration server, that canhelp you automate the running of your test cases. This helps ensure that allyour tests stay passing and your application is always ready.

Integrating a CakePHP application with Jenkins is fairly straightforward. Thefollowing assumes you’ve already installed Jenkins on *nix system, and are ableto administer it. You also know how to create jobs, and run builds. If you areunsure of any of these, refer to the Jenkins documentation .

Create a Job

Start off by creating a job for your application, and connect your repositoryso that jenkins can access your code.

Add Test Database Config

Using a separate database just for Jenkins is generally a good idea, as it stopsbleed through and avoids a number of basic problems. Once you’ve created a newdatabase in a database server that jenkins can access (usually localhost). Adda shell script step to the build that contains the following:

cat > config/app_local.php <<'CONFIG'
return [
    'Datasources' => [
        'test' => [
            'datasource' => 'Database/Mysql',
            'host'       => 'localhost',
            'database'   => 'jenkins_test',
            'username'      => 'jenkins',
            'password'   => 'cakephp_jenkins',
            'encoding'   => 'utf8'

Then uncomment the following line in your config/bootstrap.php file:

//Configure::load('app_local', 'default');

By creating an app_local.php file, you have an easy way to defineconfiguration specific to Jenkins. You can use this same configuration file tooverride any other configuration files you need on Jenkins.

It’s often a good idea to drop and re-create the database before each build aswell. This insulates you from chained failures, where one broken build causesothers to fail. Add another shell script step to the build that contains thefollowing:

mysql -u jenkins -pcakephp_jenkins -e 'DROP DATABASE IF EXISTS jenkins_test; CREATE DATABASE jenkins_test';

Add your Tests

Add another shell script step to your build. In this step install yourdependencies and run the tests for your application. Creating a junit log file,or clover coverage is often a nice bonus, as it gives you a nice graphical viewof your testing results:

# Download Composer if it is missing.
test -f 'composer.phar' || curl -sS https://getcomposer.org/installer | php
# Install dependencies
php composer.phar install
vendor/bin/phpunit --log-junit junit.xml --coverage-clover clover.xml

If you use clover coverage, or the junit results, make sure to configure thosein Jenkins as well. Failing to configure those steps will mean you won’t see theresults.

Run a Build

You should be able to run a build now. Check the console output and make anynecessary changes to get a passing build.