-
-
Notifications
You must be signed in to change notification settings - Fork 4
Test Setup
Tests in PHP are written with the PHPUnit testing framework which is the de-facto standard.
For mocking, database handling, http requests, email assertions and more, the helper traits
samuelgfeller/test-traits
are also required.
composer require --dev phpunit/phpunit
composer require --dev samuelgfeller/test-traits
The phpunit.xml
file in the root directory contains the configuration for PHPUnit.
It defines the directories that contain tests, the ones that are excluded,
and the test class suffix.
The test classes must extend the PHPUnit\Framework\TestCase
.
The APP_ENV
environment variable is set to test
.
This tells the application to use the test settings from the config/env.test.php
file.
File: phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" colors="true" backupGlobals="false"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.1/phpunit.xsd" cacheDirectory=".phpunit.cache"
backupStaticProperties="false">
<coverage/>
<testsuites>
<testsuite name="Integration">
<directory suffix="Test.php">tests/Integration</directory>
</testsuite>
<testsuite name="Unit">
<directory suffix="Test.php">tests/Unit</directory>
</testsuite>
</testsuites>
<php>
<!-- APP_ENV has to be "test" for env.test.php (test config values) to load -->
<env name="APP_ENV" value="test"/>
<env name="PHPUNIT_TEST_SUITE" value="1"/>
</php>
<source>
<include>
<directory suffix=".php">src</directory>
</include>
<exclude>
<directory>bin</directory>
<directory>build</directory>
<directory>docs</directory>
<directory>public</directory>
<directory>tmp</directory>
<directory>vendor</directory>
</exclude>
</source>
</phpunit>
The environment configuration
with test-specific values is located
in config/env.test.php
:
// Enable ErrorException for notices and warnings
$settings['error']['display_error_details'] = true;
// Database for integration testing must include the word "test"
$settings['db']['database'] = 'slim_example_project_test';
// Enable test mode for the logger
$settings['logger']['test'] = true;
// ...
Before the tests that interact with the database are run,
an empty test database must be created and its name
added to the env.test.php
file with the key $settings['db']['database']
.
The tables are created during the first setUp()
call when running the test suite.
Once they're created, they are truncated for each further test.
Tables are created using the schema.sql
file which contains the code to create the tables
of the current development database.
To generate schema.sql
, the samuelgfeller/test-traits
library contains
the SqlSchemaGenerator
class.
The file bin/console.php
must be
created and
SqlSchemaGenerator
must be added to the
container definitions in the config/container.php
file.
It requires the PDO instance which can be retrieved from the CakePHP
Connection
instance.
File: config/container.php
use Psr\Container\ContainerInterface;
use Cake\Database\Connection;
return [
// ...
// CakePHP database connection
Connection::class => function (ContainerInterface $container) {
$settings = $container->get('settings')['db'];
return new Connection($settings);
},
PDO::class => function (ContainerInterface $container) {
$driver = $container->get(Connection::class)->getDriver();
$class = new ReflectionClass($driver);
$method = $class->getMethod('getPdo');
// Make function getPdo() public
$method->setAccessible(true);
return $method->invoke($driver);
},
// String key for command line call
'SqlSchemaGenerator' => function (ContainerInterface $container) {
return new \TestTraits\Console\SqlSchemaGenerator(
$container->get(PDO::class),
// Schema output folder
$container->get('settings')['root_dir'] . '/resources/schema'
);
},
];
Now as explained in Console Commands
the generateMySqlSchema()
function can be called with the command line with the help of bin/console.php
:
php bin/console.php SqlSchemaGenerator generateMySqlSchema
To make this command available as a composer script, the following can be added
to the composer.json
file script section:
"scripts": {
"schema:generate": "php bin/console.php SqlSchemaGenerator generateMySqlSchema"
}
A new schema.sql
can now be generated with the following command:
`composer schema:generate`
Schemas for other database types can be created,
by making a copy of the SqlSchemaGenerator
class
and adjusting the SQL queries.
Traits are a way to reuse code
easily in multiple classes.
With the use
keyword after the class definition, they can be included in a class and all
their methods are available in that class as if they'd be part of the class itself.
They are ideal for functions that are used in multiple test cases.
Before each test function, the application must be
bootstrapped
and the database cleared if required.
This is done with the setUp()
function which is called automatically by PHPUnit before each
test function. The setup logic is the same for all tests, which means it can be extracted into a
trait. The same goes for the tearDown()
function which is called after each test function.
The AppTestTrait
located in tests/Traits
contains the setUp()
and tearDown()
functions.
The setUp()
function below also sets the
session
to a memory session and inserts
the user roles into the database by default.
File: tests/Traits/AppTestTrait.php
<?php
namespace App\Test\Trait;
use App\Test\Fixture\UserRoleFixture;
use Cake\Database\Connection;
use DI\Container;
use Odan\Session\MemorySession;
use Odan\Session\SessionInterface;
use Psr\Container\ContainerInterface;
use Slim\App;
use UnexpectedValueException;
trait AppTestTrait
{
use ContainerTestTrait;
protected App $app;
protected function setUp(): void
{
// Start slim app
$this->app = require __DIR__ . '/../../config/bootstrap.php';
// Set $this->container to container instance
$this->setUpContainer($this->app->getContainer());
// Set memory sessions
$this->setContainerValue(SessionInterface::class, new MemorySession());
// If setUp() is called in a testClass that uses DatabaseTestTrait, the method setUpDatabase() exists
if (method_exists($this, 'setUpDatabase')) {
// Check that database name from config contains the word "test"
// This is a double security check to prevent unwanted use of dev db for testing
if (!str_contains($container->get('settings')['db']['database'], 'test')) {
throw new UnexpectedValueException('Test database name MUST contain the word "test"');
}
// Create tables
$this->setUpDatabase($container->get('settings')['root_dir'] . '/resources/schema/schema.sql');
// If fixtureTestTrait is included in the test class, insert default user roles
if (method_exists($this, 'insertFixture')) {
// Automatically insert user roles
$this->insertDefaultFixtures([UserRoleFixture::class]);
}
}
}
protected function tearDown(): void
{
// Restore the previous error handler as PHPUnit v11 checks for any leftovers in error handlers
restore_error_handler();
// Disconnect from database to avoid "too many connections" errors
if (method_exists($this, 'setUpDatabase')) {
$connection = $this->container->get(Connection::class);
$connection->rollback();
$connection->getDriver()->disconnect();
if ($this->container instanceof Container) {
$this->container->set(Connection::class, null);
$this->container->set(\PDO::class, null);
}
}
}
}
In each test class, the AppTestTrait
is included with the use
keyword
at the top of the class.
namespace App\Test\Integration;
use PHPUnit\Framework\TestCase;
use App\Test\Trait\AppTestTrait;
class ExampleTest extends TestCase
{
use AppTestTrait;
// ...
}
Slim app basics
- Composer
- Web Server config and Bootstrapping
- Dependency Injection
- Configuration
- Routing
- Middleware
- Architecture
- Single Responsibility Principle
- Action
- Domain
- Repository and Query Builder
Features
- Logging
- Validation
- Session and Flash
- Authentication
- Authorization
- Translations
- Mailing
- Console commands
- Database migrations
- Error handling
- Security
- API endpoint
- GitHub Actions
- Scrutinizer
- Coding standards fixer
- PHPStan static code analysis
Testing
Frontend
Other