Testing basics
A test is code that checks other code: it opens a page, submits a form and compares the result with the expected one. Tests are run with one command after every change and show at once whether something broke. CakePHP uses the PHPUnit library, which is already installed in the project.
Where they live
| Folder | What it holds |
|---|---|
tests/TestCase/Controller | Controller tests |
tests/TestCase/Model/Table | Table tests |
tests/Fixture | Fixtures, the test data |
The test database
Tests work with a separate database so that real data is not damaged. Create an empty blog_test database and set it in the test connection:
// config/app_local.php
'Datasources' => [
'test' => [
'host' => 'localhost',
'username' => 'root',
'password' => 'secret',
'database' => 'blog_test',
],
],Its tables are created before the tests start, in the tests/bootstrap.php file. To build them from your migrations, replace the SchemaLoader line there with (new \Migrations\TestSuite\Migrator())->run();.
Creating a test
bin/cake bake test Controller ArticlesThe command creates tests/TestCase/Controller/ArticlesControllerTest.php with method stubs. Keep a single test in it:
// tests/TestCase/Controller/ArticlesControllerTest.php
namespace App\Test\TestCase\Controller;
use Cake\TestSuite\IntegrationTestTrait;
use Cake\TestSuite\TestCase;
class ArticlesControllerTest extends TestCase
{
use IntegrationTestTrait;
public function testIndex(): void
{
$this->get('/articles');
$this->assertResponseOk();
}
}get() makes a request to the application without a browser and assertResponseOk() checks that the response is successful. The name of a test method starts with the word test.
Running
vendor/bin/phpunitPHPUnit prints a dot for every passed test and a summary: OK (1 test, 1 assertion). On failure there is an F instead of a dot and a description of what did not match.
Fixtures
A fixture fills a table before every test and clears it afterwards. It is created with bin/cake bake fixture Articles:
// tests/Fixture/ArticlesFixture.php
namespace App\Test\Fixture;
use Cake\TestSuite\Fixture\TestFixture;
class ArticlesFixture extends TestFixture
{
public array $records = [
[
'title' => 'First article',
'body' => 'Article text.',
'published' => true,
'created' => '2026-03-01 10:00:00',
'modified' => '2026-03-01 10:00:00',
],
];
}Attach it in the test and check that the article is visible on the page:
protected array $fixtures = ['app.Articles'];
public function testIndex(): void
{
$this->get('/articles');
$this->assertResponseOk();
$this->assertResponseContains('First article');
}Checking a form
public function testAdd(): void
{
$this->enableCsrfToken();
$this->post('/articles/add', ['title' => 'New article', 'body' => 'Text']);
$this->assertRedirect(['action' => 'index']);
}enableCsrfToken() adds the form token to the request. The test checks that saving is followed by a redirect to the list.
Assertions
| Method | What it checks |
|---|---|
assertResponseOk() | A 2xx response code |
assertResponseCode(404) | A specific code |
assertResponseContains('…') | Text in the response |
assertRedirect(…) | A redirect |
Common mistake
If the test connection points to the working database, tests clear its tables. Before the first run make sure that Datasources.test holds a separate database with a different name.