---
title: Setting Up and Running PHPUnit Tests | Shopware Community Hub
description: >-
  Learn how to set up PHPUnit for a Shopware plugin, test a console command, and
  run the tests locally and in CI.
canonical_url: 'https://hub.shopware.com/learn/unit/setting-up-and-running-phpunit-tests'
---

# Setting Up and Running PHPUnit Tests

<LearningObjectives>

- Write a PHPUnit test for a Shopware command.
- Prepare a Shopware plugin test setup with `TestBootstrapper` and `autoload-dev`.
- Configure and run PHPUnit locally with the plugin's `phpunit.xml` file.
- Understand what happens during the first Shopware test environment setup.
- Know how a GitHub Actions workflow can run plugin tests in CI.

</LearningObjectives>

# Setting Up and Running PHPUnit Tests

Great to see you have made it this far! We know that writing tests is not the most exciting part of development, but it is a **crucial step** for maintaining **code quality**, **stability**, and **confidence** in your project.

In this learning unit, you will learn how to **write and run PHPUnit tests** for your **Shopware plugin**, both **locally** and in a **CI environment**.

To not bore you with too much theory, we will focus on a **real-world example** to show you how to write a PHPUnit test for a Shopware plugin. If you want to dive deeper into the topic, check out the official [PHPUnit documentation](https://docs.phpunit.de/en/12.5/index.html).

## Real-World Example

Imagine you have a **custom command** in your plugin that should return a specific value or trigger a certain workflow. You can write a **PHPUnit test** to verify that the command returns the expected value. This way, you can ensure that your command works as expected even after future changes.

## Getting Started

To write tests in Shopware, you first need a **plugin** that contains a testable component, for example, a **command**. When working with **PHPUnit**, each plugin requires:

- A **TestBootstrapper** class to prepare the Shopware test environment.
- A **tests** directory to store your test classes.
- An **autoload-dev** section in your `composer.json` file, so PHPUnit knows where to find the tests.
- A **phpunit.xml** file that tells PHPUnit which bootstrap file and test directory to use.

You can either create your own plugin from scratch or use the existing example to follow along.

### Option 1: Create Your Own Plugin

Run the following command in your shop's root directory to create a new plugin:

```bash
bin/console plugin:create MyPhpUnitPlugin
```

When prompted, select the **`Command`** option to create a testable command class out of the box.

If your generated plugin does not contain a command yet, create a small example command first. Add the file `src/Command/ExampleCommand.php` in your plugin:

```php
<?php declare(strict_types=1);

namespace MyPhpUnitPlugin\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(
    name: 'swag-commands:example',
    description: 'Demonstrates a simple testable command',
)]
class ExampleCommand extends Command
{
    // Actual code executed in the command
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $output->writeln('It works!');

        // Exit code 0 for success
        return self::SUCCESS;
    }
}
```

If your plugin has a different namespace, replace `MyPhpUnitPlugin` with your plugin namespace. The test in this unit needs a real class to test. If the command class is missing, the test cannot run successfully.

### Option 2: Use the Example Plugin

If you prefer to start with a working example, clone the following [repository](https://github.com/ShopwareAcademy/AcademyPhpUnit) into the plugins folder (`[shop_root]/custom/plugins`):

```bash
git clone git@github.com:ShopwareAcademy/AcademyPhpUnit.git custom/plugins/AcademyPhpUnit
```

This example plugin already includes:

- A command (`ExampleCommand`).
- A PHPUnit test (`ExampleCommandTest`).
- The required `TestBootstrapper` class and configuration.

<Callout title="Reference" type="info">

You can find a detailed explanation of **PHPUnit setup and configuration** in the [official documentation](https://developer.shopware.com/docs/guides/plugins/plugins/testing/php-unit.html#php-unit-testing).

</Callout>

Remember to install and activate the plugin independently of the option you chose by running the following commands in your shop's root directory.

If you created your own plugin, use:

```bash
bin/console plugin:refresh
bin/console plugin:install MyPhpUnitPlugin --activate --clearCache
```

If you cloned the example plugin, use:

```bash
bin/console plugin:refresh
bin/console plugin:install AcademyPhpUnit --activate --clearCache
```

Now you are ready to start writing your first PHPUnit test.

<Callout title="Keep One Plugin Name Consistent" type="info">

The examples below use `AcademyPhpUnit` because that is the name of the reference plugin. If you created `MyPhpUnitPlugin`, replace `AcademyPhpUnit` with `MyPhpUnitPlugin` in every related place: namespace, `addActivePlugins`, `autoload-dev`, test imports, and the `phpunit.xml` path.

</Callout>

## Parts of a PHPUnit Test

The PHPUnit setup consists of **four essential parts**. Each part has its own purpose in ensuring that your tests run smoothly and can properly interact with your plugin:

| Part                 | Description                                                                               |
|----------------------|-------------------------------------------------------------------------------------------|
| The TestBootstrapper | A PHP class that prepares the Shopware test environment.                                  |
| The tests            | PHP classes that contain your actual test cases and assertions.                           |
| autoload-dev         | A section in the `composer.json` file that tells PHPUnit where to find your test classes. |
| phpunit.xml          | The PHPUnit configuration file that points to the bootstrap file and test directory.      |

Let's take a closer look at each part.

### TestBootstrapper

The **TestBootstrapper** class is responsible for setting up the **Shopware test environment**. It ensures that all required **services**, **plugins**, and **autoloading rules** are available when running your tests.

Add a file named `TestBootstrap.php` to the `tests` directory of your plugin (`[shop_root]/custom/plugins/[Your_Plugin]/tests`).

```php
<?php declare(strict_types=1);

use Shopware\Core\TestBootstrapper;

try {
    $loader = (new TestBootstrapper())
        ->addCallingPlugin()
        ->addActivePlugins('AcademyPhpUnit') // Replace it with your plugin name if different
        ->setForceInstallPlugins(true)
        ->bootstrap()
        ->getClassLoader();
} catch (Exception $exception) {
    throw new RuntimeException(
        'Could not bootstrap the PHPUnit test environment.',
        0,
        $exception
    );
}

$loader->addPsr4('AcademyPhpUnit\\Tests\\', __DIR__);
```

<Callout title="Note" type="info">

The name inside `addActivePlugins` must match your plugin's name exactly.

If you cloned the example repository, keep it as `AcademyPhpUnit`.

If you created your own plugin with `plugin:create MyPhpUnitPlugin`, use `'MyPhpUnitPlugin'` instead.

</Callout>

If you created your own plugin, the same replacement is needed for the test namespace registration:

```php
$loader->addPsr4('MyPhpUnitPlugin\\Tests\\', __DIR__);
```

**Explanation:**

- The **`addCallingPlugin`** method registers your plugin as the one being tested.
- The **`addActivePlugins`** method registers the plugins that should be available during testing (e.g., the plugin that contains the command you want to test).
- The **`setForceInstallPlugins`** method ensures that all required plugins are automatically installed before running the tests.
- The **`bootstrap`** method prepares the Shopware testing environment.
- The **`getClassLoader`** method returns the autoloader used to load all required classes.
- The **`addPsr4`** method adds the `AcademyPhpUnit\Tests` namespace to the autoloader.
- The `try-catch` block wraps bootstrap errors in a clearer exception message. This makes setup problems easier to understand when the test environment cannot be prepared.

### The Tests

The **tests** are **PHP classes** that contain the actual **test methods** that verify your plugin's behavior. Each test method should focus on **one specific functionality** or **expected outcome**.

Below is a simple example of a PHPUnit test for a **Shopware command**:

Add this test as `tests/Command/ExampleCommandTest.php`. The `*Test.php` file name is important because PHPUnit uses this naming pattern to discover test files.

Don't forget to fix the namespace, based on what plugin name you have chosen.

```php
<?php declare(strict_types=1);

namespace AcademyPhpUnit\Tests\Command;

use AcademyPhpUnit\Command\ExampleCommand;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Tester\CommandTester;

class ExampleCommandTest extends TestCase
{
    public function testDescriptionIsCorrect(): void
    {
        $command = new ExampleCommand();
        $commandTester = new CommandTester($command);
        $commandTester->execute([]);
        $commandTester->assertCommandIsSuccessful();

        $this->assertStringContainsString('It works!', $commandTester->getDisplay());
        $this->assertSame('Demonstrates a simple testable command', $command->getDescription());
    }
}
```

**Explanation:**

- The class extends the `TestCase` class from the PHPUnit framework, which provides **assertion methods** (e.g., `assertSame`) and test lifecycle hooks.
- The `CommandTester` class simulates the running of the command and allows you to check its **output**, **status**, or **behavior**.
- `assertCommandIsSuccessful()` checks that the command finished with a successful exit code.
- `assertStringContainsString()` checks that the command printed the expected output.
- `assertSame()` ensures that the command's description matches the expected value.

You can write as many test methods as you need to cover different aspects of your command's behavior.

<Callout title="Using Your Own Plugin Name" type="info">

The code example uses the `AcademyPhpUnit` namespace because it belongs to the reference plugin.

If you created your own plugin, update the namespace and import:

```php
namespace MyPhpUnitPlugin\Tests\Command;

use MyPhpUnitPlugin\Command\ExampleCommand;
```

Also make sure the file is named `ExampleCommandTest.php`, not only `ExampleCommand.php`. Otherwise, PHPUnit may finish with `No tests executed!` because it does not discover the test class.

</Callout>

<Callout title="Tip" type="info">

As your plugin grows, tests may involve **mocking dependencies** or **stubbing services** to isolate functionality. Check out the [Shopware testing guide](https://developer.shopware.com/docs/guides/plugins/plugins/testing/php-unit.html#php-unit-testing) for more examples.

</Callout>

### The `autoload-dev` Section

The `autoload-dev` section in your `composer.json` file defines **autoloading rules** that are only used in **development environments**, such as during **testing**. This ensures that PHPUnit can **locate and load** your test classes automatically without requiring manual includes.

Add the section to your plugin's `composer.json`, not to the project root `composer.json`:

```json
"autoload-dev": {
    "psr-4": {
        "AcademyPhpUnit\\Tests\\": "tests/"
    }
}
```

The `psr-4` key defines the namespace mapping for your test classes. The namespace `AcademyPhpUnit\\Tests\\` corresponds to the `tests` directory of your plugin. When running PHPUnit, Composer will automatically use this mapping to autoload your test classes. This setup keeps your **production autoloading** (`autoload`) separate from **test autoloading** (`autoload-dev`). This ensures that test classes are **not included in production builds**.

<Callout title="Tip" type="info">

After modifying the `autoload-dev` section, you need to run `composer dump-autoload` from your shop's root directory to update the autoloader and make sure that your test classes are recognized.

</Callout>

If you created your own plugin, use your own test namespace:

```json
"autoload-dev": {
    "psr-4": {
        "MyPhpUnitPlugin\\Tests\\": "tests/"
    }
}
```

### The `phpunit.xml` File

The `phpunit.xml` file tells PHPUnit how to run the plugin tests. Add it to the root directory of your plugin, for example `[shop_root]/custom/plugins/[Your_Plugin]/phpunit.xml`.

For the example plugin, the file looks like this:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/12.5/phpunit.xsd"
         bootstrap="tests/TestBootstrap.php"
         executionOrder="random">
    <source>
        <include>
            <directory>./src/</directory>
        </include>
    </source>
    <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="AcademyPhpUnit Testsuite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
</phpunit>
```

If you created your own plugin, you can keep the same structure and only adjust the testsuite name, for example `MyPhpUnitPlugin Testsuite`. The important part is `bootstrap="tests/TestBootstrap.php"` because this is what prepares the Shopware test environment before PHPUnit discovers and runs your test classes.

## Run Tests Locally

Before running your tests, make sure you are using the **correct project template** and that **PHPUnit** is available in your environment.

<Callout title="Choose the Right Template" type="info">

The **production template** does **not** include the PHPUnit binary. If you are using it, or if `vendor/bin/phpunit` does not exist in your project, install PHPUnit manually:

```bash
composer require --dev phpunit/phpunit
composer dump-autoload
```

</Callout>

Run PHPUnit from your shop's root directory, not from inside the plugin directory.

If you cloned the example plugin, use:

```shell
vendor/bin/phpunit -c custom/plugins/AcademyPhpUnit/phpunit.xml
```

If you created your own plugin, replace the plugin folder name:

```shell
vendor/bin/phpunit -c custom/plugins/MyPhpUnitPlugin/phpunit.xml
```

This command uses the PHPUnit configuration from the plugin and executes all tests defined there. If your plugin has a different folder name, adjust the path to your own `phpunit.xml` file.

The first test run prepares the Shopware test environment. This can create or reset the test database, run migrations, refresh plugins, install the example plugin, and clear caches.

This output can be long, but it is expected. The important part is the final PHPUnit result.

If PHPUnit reports deprecations but the test status is `OK`, the test itself still passed. Deprecations should still be reviewed because they may require future code or configuration updates.

<Callout title="Test Table Not Found" type="error">

If you encounter an error like: `Base table or view not found: 1146 Table 'platform_test.app' doesn't exist`, you need to set up the database for the tests. This can be done by executing the following command in your shop's root directory: `composer init:testdb`.

</Callout>

<Callout title="Database Connection Error in Docker" type="error">

If you run the tests in a Docker-based Shopware setup and see an error such as `SQLSTATE[HY000] [2002] No such file or directory`, check the database host in your test environment configuration.

If you use a Docker setup, check the database host from the environment where PHPUnit runs. For example, if PHPUnit runs inside the Shopware/PHP container, the database host must be reachable from that container.

This error usually does not mean that PHPUnit or the `TestBootstrapper` is broken. It often means that the PHP process running PHPUnit cannot reach the database.

In Docker, `localhost` means "inside this container". If PHPUnit runs in the Shopware/PHP container and the database runs in another container, `localhost` is the wrong host. Use the database service name from your Docker Compose setup instead, for example `database` or `mysql`.

If the database really runs on your host machine, use the host address provided by your Docker setup, for example `host.docker.internal` where available. Using `127.0.0.1` can force a TCP connection instead of a MySQL Unix socket, but it still only works if the database is reachable from the environment where PHPUnit runs.

So first ask: "Where does PHPUnit run?" Then set the database host from that point of view.

</Callout>

## Run Tests in CI

To ensure that your plugin remains stable across all environments, it is best to run your PHPUnit tests in a **Continuous Integration (CI)** pipeline. This way, your tests run automatically on every **pull request**.

The example plugin uses a reusable GitHub Actions workflow from [`shopware/github-actions`](https://github.com/shopware/github-actions). This workflow provides predefined steps for setting up Shopware, preparing the database, installing the plugin, and running PHPUnit.

You will find the necessary configuration in the `.github/workflows` directory. In our **example plugin** repository, you can find the [workflow file](https://github.com/ShopwareAcademy/AcademyPhpUnit/blob/main/.github/workflows/phpunit.yml).

The workflow file contains the following important part:

```yaml
jobs:
  phpunit:
    uses: shopware/github-actions/.github/workflows/phpunit.yml@main
    with:
      extensionName: ${{ github.event.repository.name }}
      shopwareVersion: trunk
```

The `uses` line tells GitHub Actions to reuse Shopware's PHPUnit workflow. The `extensionName` value tells the workflow which plugin repository should be tested. The `shopwareVersion` value defines which Shopware version the test environment should use.

<Callout title="Tip" type="info">

Make sure your CI environment includes **all required dependencies** (e.g., database, PHP extensions) and uses the same **Shopware version** as your local setup for consistent test results.

</Callout>

## Summary

In this learning unit, you have learned:

- How to use an example plugin as a starting point for PHPUnit tests.
- How to prepare the Shopware test environment with **TestBootstrapper**.
- How **autoload-dev** makes plugin test classes available to PHPUnit.
- How `phpunit.xml` connects PHPUnit to the plugin bootstrap and test directory.
- How to test a Symfony console command with `CommandTester`.
- How to run PHPUnit locally with the plugin's `phpunit.xml` configuration.
- Why the first test run may prepare the test database, run migrations, refresh plugins, and clear caches.
- How a GitHub Actions workflow can run plugin tests in a CI environment.

With this knowledge, you can confidently integrate PHPUnit tests into your Shopware development workflow and ensure your plugins remain stable and reliable.

<Callout title="Outlook PHPUnit (Intermediate)" type="info">

If you want to go deeper into PHPUnit testing, continue with the **Backend Development Intermediate** learning path.

</Callout>

---

Well done! You have completed this course and with it the entire **Backend Development Essentials** learning path. You now have a solid foundation to start developing your own Shopware plugins.

From here, you can continue seamlessly with the [Shopware Backend Development Intermediate](/learn/path/shopware-backend-development-intermediate) learning path to deepen your knowledge.
