Zum Inhalt springen

Effektives Testing mit PHPUnit: Symfony-Projekte und Shopware-6-Plugins

Veröffentlicht am 20. Jan. 2026 | ca. 1 Min. Lesezeit |

Tests sind kein optionaler Luxus, sondern eine Investition in die Qualität und Wartbarkeit Ihres Codes. In Symfony-Projekten bietet PHPUnit dafür alle nötigen Werkzeuge.

Die Testpyramide

Eine gesunde Teststruktur folgt der Testpyramide:

  • Unit-Tests (viele) – Testen einzelne Klassen isoliert
  • Integration-Tests (einige) – Testen das Zusammenspiel mehrerer Komponenten
  • Functional Tests (wenige) – Testen komplette HTTP-Requests

Unit-Tests: Services isoliert testen

Ein Service sollte unabhängig von Infrastruktur testbar sein:

<?php

declare(strict_types=1);

namespace App\Tests\Service;

use App\Service\PriceCalculator;
use PHPUnit\Framework\TestCase;

class PriceCalculatorTest extends TestCase
{
    private PriceCalculator $calculator;

    protected function setUp(): void
    {
        $this->calculator = new PriceCalculator();
    }

    public function testCalculateNetPrice(): void
    {
        $grossPrice = 119.0;
        $taxRate = 19.0;

        $netPrice = $this->calculator->calculateNet($grossPrice, $taxRate);

        $this->assertEqualsWithDelta(100.0, $netPrice, 0.01);
    }

    public function testCalculateWithDiscount(): void
    {
        $grossPrice = 100.0;
        $discount = 10.0;

        $result = $this->calculator->applyDiscount($grossPrice, $discount);

        $this->assertEquals(90.0, $result);
    }
}

Integration-Tests: Datenbank einbeziehen

Für Tests mit Datenbankzugriff bietet Symfony den KernelTestCase:

<?php

declare(strict_types=1);

namespace App\Tests\Repository;

use App\Entity\Product;
use App\Repository\ProductRepository;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class ProductRepositoryTest extends KernelTestCase
{
    private ProductRepository $repository;

    protected function setUp(): void
    {
        self::bootKernel();
        $this->repository = static::getContainer()
            ->get(ProductRepository::class);
    }

    public function testFindActiveProducts(): void
    {
        $products = $this->repository->findActive();

        $this->assertNotEmpty($products);
        foreach ($products as $product) {
            $this->assertTrue($product->isActive());
        }
    }
}

Functional Tests: Controller testen

Mit dem WebTestCase können Sie komplette HTTP-Requests simulieren:

<?php

declare(strict_types=1);

namespace App\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class ProductControllerTest extends WebTestCase
{
    public function testProductListReturns200(): void
    {
        $client = static::createClient();
        $client->request('GET', '/api/products');

        $this->assertResponseIsSuccessful();
        $this->assertResponseHeaderSame('content-type', 'application/json');
    }

    public function testCreateProductRequiresAuth(): void
    {
        $client = static::createClient();
        $client->request('POST', '/api/products');

        $this->assertResponseStatusCodeSame(401);
    }
}

Data Providers für parametrisierte Tests

Data Providers reduzieren Duplikation bei ähnlichen Testfällen:

public static function invalidPriceProvider(): array
{
    return [
        'negative price' => [-10.0],
        'zero price' => [0.0],
        'extremely high' => [999999999.99],
    ];
}

#[\PHPUnit\Framework\Attributes\DataProvider('invalidPriceProvider')]
public function testRejectsInvalidPrices(float $price): void
{
    $this->expectException(\InvalidArgumentException::class);
    $this->calculator->validatePrice($price);
}

Shopware 6: Testing in der Plugin-Entwicklung

Shopware 6 folgt ebenfalls der Testpyramide, nutzt aber ein eigenes Test-Setup mit spezialisierten Base-Classes und Traits. Die Testlandschaft sieht so aus:

Schicht Framework Einsatz
PHP Backend PHPUnit Unit-Tests, Integration-Tests, Migration-Tests
Administration (Vue) Jest Admin-Komponenten-Tests
Storefront JS Jest Storefront-JavaScript-Tests
E2E / Akzeptanz Playwright End-to-End-Tests (seit Ende 2023, ersetzt Cypress)

PHPUnit-Setup für Plugins

Jedes Shopware-Plugin kann eigene PHPUnit-Tests mitbringen. Das Setup beginnt mit einer phpunit.xml im Plugin-Root und einer Bootstrap-Datei, die den Shopware-Kernel startet:

<?php

declare(strict_types=1);

use Shopware\Core\TestBootstrapper;

$loader = (new TestBootstrapper())
    ->addCallingPlugin()
    ->addActivePlugins('MeinPlugin')
    ->setForceInstallPlugins(true)
    ->bootstrap()
    ->getClassLoader();

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

setForceInstallPlugins(true) stellt sicher, dass das Plugin auch dann installiert und aktiv bleibt, wenn die Testdatenbank bereits existiert.

Integration-Tests mit IntegrationTestBehaviour

Das Trait IntegrationTestBehaviour ist das Arbeitspferd für Shopware-Plugin-Tests. Es bietet automatische Datenbank-Transaktionen (Rollback nach jedem Test), Cache-Clearing und Container-Zugriff:

<?php

declare(strict_types=1);

namespace MeinPlugin\Tests;

use PHPUnit\Framework\TestCase;
use Shopware\Core\Framework\Test\TestCaseBase\IntegrationTestBehaviour;

class MeinServiceTest extends TestCase
{
    use IntegrationTestBehaviour;

    public function testServiceIstRegistriert(): void
    {
        $service = $this->getContainer()->get(MeinService::class);

        $this->assertInstanceOf(MeinService::class, $service);
    }
}

Für Migration-Tests gibt es das schlankere KernelTestBehaviour, das nur Container-Zugriff ohne automatische Transaktions-Rollbacks bietet.

Tests ausführen

# Alle Plugin-Tests
vendor/bin/phpunit --configuration="custom/plugins/MeinPlugin"

# Nur Migrations-Tests
vendor/bin/phpunit --configuration="custom/plugins/MeinPlugin" --testsuite "migration"

# Jest-Tests für die Administration
composer run admin:unit

# Jest-Tests für die Storefront
composer run storefront:unit

E2E-Tests mit Playwright

Shopware hat Ende 2023 per ADR die Migration von Cypress auf Playwright beschlossen. Die Gründe: bessere Performance, stabilere Tests und integriertes Tracing für CI/CD. Das alte Cypress-Repository (@shopware-ag/e2e-testsuite-platform) ist archiviert.

Die neue Acceptance Test Suite basiert auf Playwright und bietet vorgefertigte Fixtures:

import { test, expect } from '@shopware-ag/acceptance-test-suite';

test('Produktanlage im Admin', async ({ ShopAdmin, AdminProductCreate }) => {
    await ShopAdmin.goesTo(AdminProductCreate.url());
    await ShopAdmin.expects(AdminProductCreate.nameInput).toBeVisible();
    await ShopAdmin.expects(AdminProductCreate.saveButton).toBeVisible();
});

test('Testdaten per API erstellen', async ({ TestDataService }) => {
    const product = await TestDataService.createProductWithImage({
        description: 'Test-Beschreibung'
    });
    expect(product.description).toEqual('Test-Beschreibung');
    expect(product.coverId).toBeDefined();
});

Setup:

npm init playwright@latest
npm install @shopware-ag/acceptance-test-suite
npx playwright install

Die Suite bietet ShopAdmin- und ShopCustomer-Akteure, einen AdminApiContext für API-Aufrufe und einen TestDataService für die programmatische Testdaten-Erstellung.

Fazit

Gute Tests geben Vertrauen bei Refactorings und sparen langfristig deutlich mehr Zeit als sie kosten. Starten Sie mit den kritischsten Pfaden und erweitern Sie die Abdeckung schrittweise. In Shopware-Plugins lohnt sich der Einstieg über Integration-Tests mit IntegrationTestBehaviour — das Trait nimmt Ihnen das meiste Setup-Boilerplate ab.

Thomas Wunner

Thomas Wunner

Fachinformatiker für Anwendungsentwicklung mit Ausbildereignungsprüfung und über 14 Jahre Erfahrung im Aufbau skalierbarer Webanwendungen mit Symfony und Shopware. Abseits der Tastatur ist Thomas als Rettungsschwimmer in der Wasserwacht aktiv, legt als DJ auf und erkundet die Umgebung auf dem Motorrad.

Kommentare

Kommentare werden von Remark42 bereitgestellt. Beim Laden werden Daten an unseren Kommentar-Server übertragen.