PHP Unit Testing
Overview
This guide will cover the creation of PHPUnit tests in Shopware 6. Refer to the official PHPUnit documentation for a deep dive into PHP unit testing.
Prerequisites
In order to create tests for a plugin, you need a plugin as a base. Refer to the Plugin Base Guide for more information.
Furthermore, have a look at our Execute database queries/migrations guide since this guide will show you how to create a migration test for these examples.
PHPUnit configuration
First, to configure PHPUnit, create a file called phpunit.xml
in the root directory of the plugin. To get more familiar with the configurable options, refer to the PHPUnit documentation. This example explains configuring PHPUnit to search in the directories <plugin root>/src/Test
and <plugin root>/src/Migration/Test
for your tests.
The phpunit.xml
can be autogenerated for you with the bin/console plugin:create
command:
// <plugin root>/phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
bootstrap="tests/TestBootstrap.php"
executionOrder="random">
<coverage>
<include>
<directory>./src/</directory>
</include>
</coverage>
<php>
<ini name="error_reporting" value="-1"/>
<server name="KERNEL_CLASS" value="Shopware\Core\Kernel"/>
<env name="APP_ENV" value="test"/>
<env name="APP_DEBUG" value="1"/>
<env name="SYMFONY_DEPRECATIONS_HELPER" value="weak"/>
</php>
<testsuites>
<testsuite name="migration">
<directory>Migration/Test</directory>
</testsuite>
<testsuite name="Example Testsuite">
<directory>Test</directory>
</testsuite>
</testsuites>
</phpunit>
This command will also generate a TestBootstrap.php
file:
// <plugin root>/tests/TestBootstrap.php
<?php declare(strict_types=1);
use Shopware\Core\TestBootstrapper;
$loader = (new TestBootstrapper())
->addCallingPlugin()
->addActivePlugins('BasicExample')
->setForceInstallPlugins(true)
->bootstrap()
->getClassLoader();
$loader->addPsr4('Swag\\BasicExample\\Tests\\', __DIR__);
The setForceInstallPlugins
method ensures that your plugin is installed and active even the test database was already build beforehand.
Example Tests
Integration test
After PHPUnit is configured, a first test can be written. In this example, a test simply tries to instantiate every .php
class to see if any used core classes are missing. In the test, you use the IntegrationTestBehaviour
trait, which comes with some handy features, such as automatically setting up a database transaction or clearing the cache before starting tests. This is how your test could look like:
// <plugin root>/src/Test/UsedClassesAvailableTest.php
<?php declare(strict_types=1);
namespace Swag\BasicExample\Test;
use PHPUnit\Framework\TestCase;
use Shopware\Core\Framework\Test\TestCaseBase\IntegrationTestBehaviour;
use Symfony\Component\Finder\Finder;
class UsedClassesAvailableTest extends TestCase
{
use IntegrationTestBehaviour;
public function testClassesAreInstantiable(): void
{
$namespace = str_replace('\Test', '', __NAMESPACE__);
foreach ($this->getPluginClasses() as $class) {
$classRelativePath = str_replace(['.php', '/'], ['', '\\'], $class->getRelativePathname());
$this->getMockBuilder($namespace . '\\' . $classRelativePath)
->disableOriginalConstructor()
->getMock();
}
// Nothing broke so far, classes seem to be instantiable
$this->assertTrue(true);
}
private function getPluginClasses(): Finder
{
$finder = new Finder();
$finder->in(realpath(__DIR__ . '/../'));
$finder->exclude('Test');
return $finder->files()->name('*.php');
}
}
Migration test
In order to test the example migration Migration1611740369ExampleDescription
, create a new test called Migration1611740369ExampleDescriptionTest
, which extends from the PHPUnit TestCase
. Use the KernelTestBehaviour
trait because a database connection from the container is needed.
This is an example for a migration test:
// <plugin root>/src/Migration/Test/Migration1611740369ExampleDescriptionTest.php
<?php declare(strict_types=1);
namespace Swag\BasicExample\Migration\Test;
use Doctrine\DBAL\Connection;
use PHPUnit\Framework\TestCase;
use Shopware\Core\Framework\Test\TestCaseBase\KernelTestBehaviour;
class Migration1611740369ExampleDescriptionTest extends TestCase
{
use KernelTestBehaviour;
public function testNoChanges(): void
{
/** @var Connection $conn */
$conn = $this->getContainer()->get(Connection::class);
$expectedSchema = $conn->fetchAssoc('SHOW CREATE TABLE `swag_basic_example_general_settings`')['Create Table'];
$migration = new Migration1611740369ExampleDescription();
$migration->update($conn);
$actualSchema = $conn->fetchAssoc('SHOW CREATE TABLE `swag_basic_example_general_settings`')['Create Table'];
static::assertSame($expectedSchema, $actualSchema, 'Schema changed!. Run init again to have clean state');
$migration->updateDestructive($conn);
$actualSchema = $conn->fetchAssoc('SHOW CREATE TABLE `swag_basic_example_general_settings`')['Create Table'];
static::assertSame($expectedSchema, $actualSchema, 'Schema changed!. Run init again to have clean state');
}
public function testNoTable(): void
{
/** @var Connection $conn */
$conn = $this->getContainer()->get(Connection::class);
$conn->executeStatement('DROP TABLE `swag_basic_example_general_settings`');
$migration = new Migration1611740369ExampleDescription();
$migration->update($conn);
$exists = $conn->fetchColumn('SELECT COUNT(*) FROM `swag_basic_example_general_settings`') !== false;
static::assertTrue($exists);
}
}
Mocking services
In some cases a service should behave differently in a test run. Such a case could be where a service deletes a file or makes a critical api call. To avoid this in a test run it is possible to create a <plugin root>/Resources/config/services_test.{xml|yml}
file which will override your <plugin root>/Resources/config/services.{xml|yml}
. But only for the test environment.
In this test-only service config you can override arguments, aliases or parameters to change what the service container injects into services during a test run.
Executing the test
To execute tests, a PHPUnit binary is necessary, which is most likely located in the vendor/bin
folder. The command below will use the phpunit.xml
file in the custom/plugins/SwagBasicExample
folder and execute the testsuite with the name migration
.
// <project root>ell
./vendor/bin/phpunit --configuration="custom/plugins/SwagBasicExample" --testsuite "migration"
Executing all tests in the plugin
If no testsuite is passed, it will execute all testsuites.
./vendor/bin/phpunit --configuration="custom/plugins/SwagBasicExample"
Executing a single class or method
To execute a specific test class or method of a testsuite, pass the argument --filter
with the name of the class or method.
./vendor/bin/phpunit --configuration="custom/plugins/SwagBasicExample" --filter testNoChanges
./vendor/bin/phpunit --configuration="custom/plugins/SwagBasicExample" --filter Migration1611740369ExampleDescriptionTest
Flex template
In order to run PHPunit tests install the flex template dev-tools package via composer.
composer require --dev dev-tools
Next steps
Running unit tests with javascript code is explained in the following two articles: