Skip to content

PHP Unit Testing

PHP Unit Testing

This guide covers creating PHPUnit tests in Shopware 6. Refer to the official PHPUnit documentation for a deep dive into PHP unit testing.

Prerequisites

To create tests for a plugin, you need a plugin as a base. Refer to the Plugin Base Guide for more information.

Furthermore, refer to migrations guide 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 how to configure PHPUnit to search for your tests in the directories <plugin root>/src/Test and <plugin root>/src/Migration/Test.

The phpunit.xml can be autogenerated for you with the bin/console plugin:create command:

xml
// <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:

php
// <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 if the test database was already built beforehand.

Example Tests

Integration test

After PHPUnit is configured, the first test can be written. In this example, a test simply tries to instantiate every .php class to see if any of the core classes are missing. In the test, you use the IntegrationTestBehaviour trait, which provides handy features, such as automatically setting up a database transaction or clearing the cache before each test. This is how your test could look like:

php
// <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 of a migration test:

php
// <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, you can create a <plugin root>/Resources/config/services_test.{xml|yml} file that overrides 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.

sh
// <project root>
./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.

shell
./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.

shell
./vendor/bin/phpunit --configuration="custom/plugins/SwagBasicExample" --filter testNoChanges
./vendor/bin/phpunit --configuration="custom/plugins/SwagBasicExample" --filter Migration1611740369ExampleDescriptionTest

Flex template

To run PHPunit tests install the flex template dev-tools package via composer.

shell
composer require --dev dev-tools
Was this page helpful?
UnsatisfiedSatisfied
Be the first to vote!
0.0 / 5  (0 votes)