The only way to go fast, is to go well, my Uncle Bob always said. Research has shown that development with TDD evolves 10% faster than work without TDD. [See here]
CakePHP comes with comprehensive testing support built-in with integration for PHPUnit. It also offers some additional features to make testing easier.
This article will cover how to write Unit Tests with CakePHP and using the CakeDC DbTest plugin.
First, let's bake a new project:
composer create-project --prefer-dist cakephp/app:4.*
Now, we need to think about a model so we can create it and test it. I guess everybody has written a Products model before, our model would looks like this:
-
Name (string)
-
Slug (string, unique)
-
Description (text)
-
Price (decimal)
If you are not familiar with Slug, Slug is the part of a URL that identifies a page in a human-readable way, usually for pages with friendly urls. It will be the target of our tests.
bin/cake bake migration CreateProducts name:string slug:string:unique price:decimal[5,2] description:text created modified
Pay attention, for slug, It was created with a unique index. Meanwhile our goal will be to have urls like: /slug-of-product and this way, the slug needs to be unique.
Let's run the migrations for database:
bin/cake migrations migrate
At this point, our database is ready with the `products` table and we can start coding and writing the tests.
* Note: some points were abstracted, such as installation, project configuration, and shell commands, because that's not the goal of the article. You can find all information on these in the cookbook.
Let's bake the models, controller, and templates for Product:
bin/cake bake all Products
Now that we have all the Classes we can start writing the unit tests. Let's start with ProductsController, writing one test for add Product:
tests/TestCase/Controller/ProductsControllerTest.php
public function testAdd(): void
{
$this->enableCsrfToken();
$this->enableRetainFlashMessages();
$this->post('products/add', [
'name' => 'iPhone 11',
'slug' => 'iphone-11',
'price' => 699,
'description' => 'Lorem ipsum dolor sit amet, aliquet feugiat.',
]);
$this->assertResponseSuccess();
$this->assertFlashMessage(__('The product has been saved.'));
$this->assertRedirect('products');
}
Let's write another test that tries to add a duplicated product. First, we need to update the fixture, then write the test:
tests/Fixture/ProductsFixture.php
public function init(): void
{
$this->records = [
[
'id' => 1,
'name' => 'iPhone SE',
'slug' => 'iphone-se',
'price' => 399,
'description' => 'Lorem ipsum dolor sit amet, aliquet feugiat.',
'created' => '2020-04-23 13:12:58',
'modified' => '2020-04-23 13:12:58',
],
];
parent::init();
}
tests/TestCase/Controller/ProductsControllerTest.php
public function testAddDuplicated(): void
{
$this->enableCsrfToken();
$this->enableRetainFlashMessages();
$this->post('products/add', [
'name' => 'iPhone SE',
'slug' => 'iphone-se',
'price' => 399,
'description' => 'Lorem ipsum dolor sit amet, aliquet feugiat.',
]);
$this->assertResponseSuccess();
$this->assertFlashMessage(__('The product could not be saved. Please, try again.'));
$this->assertNoRedirect();
}
With these tests, we know the work is complete when the acceptance criteria (the slug of product must be unique) of the tests is passed.
That's all? No, this article it's not only about tests, this article is about the CakeDC DbTest plugin and how it can be advantageous.
CakeDC DB Test
Maintaining fixtures on real applications can be hard, a big headache. Imagine writing +1k products on ProductFixture, and adding a relationship like Product belongs to Category, then having to write new fixtures and keep them in sync.
Real applications usually have features like authentication with ACL, where each User has one Role, and each Role can access many features. Administrator has full rights, Manager has many rights, and so on.
Keeping all of this information in our fixtures is painful. Most of the frameworks have plugins to help with that issue. Thanks to the CakeDC team, we can easily let the DbTest to do the "dirty" work for us:
Let's install and load the plugin:
composer require cakedc/cakephp-db-test:dev-2.next
bin/cake plugin load CakeDC/DbTest
Then configure the plugin on project:
-
Copy/replace the phpunit.xml: https://github.com/CakeDC/cakephp-db-test/blob/2.next/phpunit.xml.dbtest
-
Configure test_template datasource on config/app.php:
'Datasources' => [
// ...
'test_template' => [
'className' => Connection::class,
'driver' => Mysql::class,
'persistent' => false,
'timezone' => 'UTC',
//'encoding' => 'utf8mb4',
'flags' => [],
'cacheMetadata' => true,
'quoteIdentifiers' => false,
'log' => false,
//'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
],
// ...
Now, we can delete our fixture and generate the dump of our database for using on tests:
// migrate the database for template
bin/cake migrations migrate -c test_template
// import fixtures
bin/cake fixture_import dump
// generate dump
/bin/cake db_test -i
Finally, we can see some advantages of CakeDC DbTest:
-
Speed.
-
Maintain fixtures with your regular database tool.
-
Run migrations to your dbtest db too.
-
Copy data from your live db to reproduce bugs.
That's all, bakers. Now we have test_db.sql, and you can see how our fixtures will come from this data.
You can check the code source of this article on this repository: https://github.com/rafaelqueiroz/cakephp-db-test-sample