Unit Tests
The main task of tests is to ensure the maximum reliability of the code. The most important rule to remember is that a test should bring the system to a reference state, cover business logic, and verify that all data is correct, then return the system to the reference state. Try to make all your tests as isolated as possible from other tests.
Usually, tests are located in the tests
folder at the root of the project.
Examples of how to write tests for DGS can be found in vendor/festi-team/festi-framework-core/tests
.
Base Structure
/var/www/[USER_NAME]/data/www/
:
[PROJECT_NAME]/
|...
|-- tests/
|-- Plugins/
| |-- CompaniesPluginTest.php
| |-- UsersPluginTest.php
| ...
|-- Resources/
|-- Responses/
| |-- GitlabProjects.json
| ...
| ...
|-- bootstrap.php
|-- phpunit.xml
|-- [PROJECT_NAME]TestCase.php
Get Started
- Append to
composer.json
:
"autoload-dev": {
"psr-4": {
"": [
"tests",
"vendor/festi-team/festi-framework-core/tests"
]
}
}
- Create
bootstrap.php
:
You can use external variables DB_ROOT_USER
, DB_ROOT_PASSWORD
to run tests from gitlab pipelines.
export DB_ROOT_USER="user_with_root_privileges"
export DB_ROOT_PASSWORD="Password123!"
vendor/bin/phpunit
tests/bootstrap.php
<?php
define('PHPUnit', true);
define('FS_ROOT', realpath(__DIR__.'/../').DIRECTORY_SEPARATOR);
define('FS_TESTS_ROOT', __DIR__.DIRECTORY_SEPARATOR);
if (!defined('MYSQL_PATH')) {
define('MYSQL_PATH', getenv('MYSQL_PATH') ?: '/usr/bin/mysql');
}
$GLOBALS['config'] = array();
require_once 'vendor/autoload.php';
// using manual configuration for test database(usually located in tests/config.php)
$GLOBALS['config']['db']['dsn'] = $GLOBALS['DB_DSN'];
$GLOBALS['config']['db']['user'] = $GLOBALS['DB_USER'];
$GLOBALS['config']['db']['pass'] = $GLOBALS['DB_PASSWD'];
// using auto configuration for test database(creating and dropping)
$GLOBALS['config']['db'] = (new TestDatabase(DataAccessObject::TYPE_MYSQL, MYSQL_PATH))->initialize(
getenv('DB_ROOT_USER') ?: 'root',
getenv('DB_ROOT_PASSWORD') ?: 'password'
);
$GLOBALS['FS_ROOT'] = FS_ROOT;
require_once FS_ROOT."common.php";
require_once __DIR__.DIRECTORY_SEPARATOR.'[PROJECT_NAME]TestCase.php';
$options = array(
Core::OPTION_THEME_NAME => 'default',
Core::OPTION_ENGINE_FOLDER => 'core',
Core::OPTION_PLUGINS_FOLDER => 'plugins',
Core::OPTION_CONNECTION => DataAccessObject::factory($db),
'convert_path' => '/usr/bin/convert',
);
$core = Core::getInstance($options);
$core->config = $GLOBALS['config'];
$core->user = new DefaultUser($GLOBALS['_sessionData']);
TestUtils::cleanDatabase();
TestUtils::installDatabase(FS_ROOT.'dump'.DIRECTORY_SEPARATOR);
$systemPlugin = $core->getPluginInstance('Jimbo');
$core->setSystemPlugin($systemPlugin);
$systemPlugin->onInitRequest();
If
TestUtils::installDatabase
throws an error, you need to declare theMYSQL_PATH
constant inbootstrap.php
.define('MYSQL_PATH', '/usr/bin/mysql');
- Create
phpunit.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
bootstrap="./bootstrap.php"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
forceCoversAnnotation="false"
verbose="true"
>
<testsuites>
<testsuite name="Plugins">
<directory>./Plugins/</directory>
</testsuite>
</testsuites>
<filter>
<blacklist>
<directory>../tests/</directory>
<directory>../themes/</directory>
<directory>../vendor/</directory>
</blacklist>
</filter>
<php>
<var name="DB_DSN" value="mysql:dbname=festi_tests;host=localhost" />
<!-- <var name="DB_DSN" value="sqlsrv:Server=;Database=festi_tests" /> -->
<!-- <var name="DB_DSN" value="pgsql:dbname=festi_tests;host=localhost" /> -->
<var name="DB_USER" value="developer" />
<var name="DB_PASSWD" value="developertest" />
<var name="DB_DBNAME" value="festi_tests" />
</php>
<logging>
<log type="junit" target="./reports/phpunit.xml" />
<log type="coverage-clover" target="./reports/phpunit.coverage.xml" />
</logging>
</phpunit>
Don't forget that tests should be run on another database, and you should triple-check that you don't accidentally run tests on your production database.
- It is usually convenient to have a parent class for tests that all tests in the project inherit from. This class is
often named
tests/[PROJECT_NAME]TestCase.php
.
class [PROJECT_NAME]TestCase extends FestiTestCase
{
protected function setUp()
{
$this->core = Core::getInstance();
} // end setUp
protected function tearDown()
{
}
}
- Create tests/Plugins and put all tests related to plugins there, for example:
tests/Plugins/AppPluginTest.php
class AppPluginTest extends [PROJECT_NAME]TestCase
{
public function testOnVersionGet()
{
$appPlugin = $this->core->getPluginInstance('App');
$apps = $appPlugin->object->getApps();
$this->assertNotEmpty($apps);
foreach ($apps as $app) {
$this->assertArrayHasKey('version', $app);
}
} // end testOnVersionGet
}
- Run Unit test:
cd tests
phpunit
You can run specific test:
phpunit Plugins/AppPluginTest.php
Mock Plugin
Often when writing tests, you need to mock a method call that, for example, loads remote data from some API.
The FestiTestCase class has a getPluginMockBuilder
method that generates a MockObject
for the plugin.
Example:
$appPlugin = $this->getPluginMockBuilder('App', array('getLicenseValuesObject'));
$appPlugin->method('getLicenseValuesObject')
->willReturn('foo');
$response = new Response();
$appApiPlugin->onDownloadGet($response);
$this->assertTrue($response->status);
Mock Action
Method getActionMockBuilder
generate MockObject for action:
$actionMock = $this->getActionMockBuilder(
'CsvImport',
array('getUploadFilePath')
);
Unittest for plugins
Autogenerator Unit tests for Store
To cover all actions
in DGS, you can use the doTestStore
method.
class AutoCreateStoreTest extends FestiTestCase
{
public function testCreateStoreTests()
{
$this->doTestStore("site_contents");
}
}
The method generates the tested methods for the main actions: list, insert, edit, info, remove.
If an unknown action
is encountered, you will need to create a method in your class
onTest[ACTION_NAME]StoreAction($store, $values)
that should cover this action
Working with DGS`s Child Relation
$values = array(
'column_name' => 'test_column',
'type' => 'string'
);
$parentValues = array(
'id_storage' => $storageData['id']
);
static::prepareChildRelationPostByStoreName(
"game_world_app_storage_columns",
$storageData['id'],
$values,
$parentValues
);
$store = $this->plugin->yourPluginName->onDisplayStorageFields($response);
list($data) = $store->load();
$this->assertTrue($data['column_name'] == "test_column");
Theme tests
To write tests for a theme, you need to have a tests
folder in the root of your theme, and you need to write tests for
the theme there.
We recommend inheriting tests from the ThemeTestCase
class.
class HeaderThemeTest extends ThemeTestCase
{
public function testAddContentRight(): void
{
$testContent = 'testAddContentRight';
$this->core->addEventListener(
Theme::EVENT_THEME_HEADER_CONTENT_RIGHT,
function (FestiEvent &$event) use ($testContent) {
return $testContent;
}
);
$content = '';
$displayPlugin = new DisplayPlugin();
$result = $displayPlugin->fetchMain($content);
$this->assertTrue(is_int(strpos($result, $testContent)));
}
}
FAQ
Environment Variables
To set custom database access, you can use environment variables:
DB_TYPE
- Type of database (mysql, mssql, pgsql). Default: mysqlDB_USER
- Default: developerDB_PASSWORD
- Default: developertestDB_NAME
- Default: festi_testsDB_HOST
- Default: localhostDB_PORT
- Default:null
- the standard port for the database type will be used.
export DB_NAME=festi_tests2
cd tests
php ../vendor/bin/phpunit
SQL Server
CREATE DATABASE festi_tests;
CREATE LOGIN developer WITH PASSWORD = 'developertest1@';
CREATE USER developer FOR LOGIN developer;
USE festi_tests;
EXEC sp_addrolemember 'db_owner', 'developer';
Permission
If you need to test logic related to permission checks through the Permission Section, you can do it like this:
public function test_onDisplayManage()
{
$gamesPlugin = $this->core->getPluginInstance('Games');
$response = new Response();
$hasException = false;
try {
$gamesPlugin->onDisplayManage($response);
} catch (PermissionsException $exp) {
$hasException = true;
}
$this->assertTrue($hasException);
$this->core->getSystemPlugin()->setPermissionSection(
'manage_games',
ISystemPlugin::PERMISSION_MASK_EXECUTE,
$this->core->user->getRole()
);
$this->core->getSystemPlugin()->refreshPermissionSections();
$gamesPlugin->onDisplayManage($response);
$this->assertNotEmpty($response->getContent());
}
Usage annotations
In our testing practices, we have made a conscious decision not to utilize annotations provided by PHPUnit. This decision is based on our desire to avoid potential compatibility issues with different versions of PHPUnit and to maintain flexibility in our testing framework.
By not relying on annotations, we ensure that our test cases remain independent of the specific version of PHPUnit being used. This allows us to update PHPUnit or switch to alternative testing frameworks in the future without impacting our existing tests.
Instead of using annotations, we structure our tests to provide data directly within the test methods. This approach allows us to have greater control over the test data and enhances the readability and maintainability of our test suite.
We believe that this approach provides a more robust and future-proof testing strategy for our project.