algoritma/shopware-test-utils

A collection of helpers for Shopware 6 integration/functional tests.

Installs: 46

Dependents: 0

Suggesters: 0

Security: 0

Stars: 1

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/algoritma/shopware-test-utils

dev-main 2026-01-12 16:36 UTC

This package is auto-updated.

Last update: 2026-01-12 16:36:13 UTC


README

A comprehensive collection of helpers, factories, and utilities for Shopware 6 integration and functional tests.

PHP Version Shopware Version License

๐Ÿ“ฆ Installation

composer require --dev algoritma/shopware-test-utils

๐ŸŽฏ Overview

This library provides a structured, clean, and maintainable approach to writing tests for Shopware 6 projects. It follows strict Single Responsibility Principle (SRP) and the Factory/Helper/Trait pattern to ensure separation of concerns.

Core Philosophy

"Factory CREATES, Helper ACTS, Trait ASSERTS"

  • Factories โ†’ Create and configure entities (Products, Orders, Customers, etc.)
  • Helpers โ†’ Execute actions on existing entities (place order, cancel, transition states, configuration, time travel)
  • Traits โ†’ Provide test assertions (database checks, event verification, mail assertions)

๐Ÿš€ Quick Start

Basic Integration Test

<?php

use Algoritma\ShopwareTestUtils\Core\AbstractIntegrationTestCase;
use Algoritma\ShopwareTestUtils\Factory\ProductFactory;
use Algoritma\ShopwareTestUtils\Factory\CustomerFactory;

class OrderPlacementTest extends AbstractIntegrationTestCase
{
    public function testUserCanPlaceOrder(): void
    {
        // Create test entities using Factories
        $customer = (new CustomerFactory($this->getContainer()))
            ->withEmail('test@example.com')
            ->withFirstName('John')
            ->withLastName('Doe')
            ->create();

        $product = (new ProductFactory($this->getContainer()))
            ->withName('Test Product')
            ->withPrice(19.99)
            ->withStock(100)
            ->create();

        $context = $this->createAuthenticatedContext($customer);

        // Create cart and add product
        $cart = $this->createCart($context)
            ->withProduct($product->getId())
            ->create();

        // Place order using Helper
        $order = $this->placeOrder($cart, $context);

        // Assert order was created
        $this->assertOrderState($order, 'open');
        $this->assertDatabaseHas('order', ['id' => $order->getId()]);
    }
}

Functional/Storefront Test

<?php

use Algoritma\ShopwareTestUtils\Core\AbstractFunctionalTestCase;

class StorefrontCheckoutTest extends AbstractFunctionalTestCase
{
    public function testCheckoutFlow(): void
    {
        // Create storefront request helper
        $storefront = $this->createStorefrontHelper();

        // Simulate user actions
        $storefront->addProductToCart($productId);
        $storefront->goToCheckout();
        $response = $storefront->submitOrder();

        $this->assertResponseIsSuccessful($response);
        $this->assertMailSent('order_confirmation');
    }
}

๐Ÿ“š Available Components

๐Ÿญ Factories (Create Entities)

Factories use the Builder pattern with magic methods to create and configure entities with an ultra-fluent API.

All factories extend AbstractFactory providing:

  • โœจ Magic methods: with*() and set*() for any property
  • ๐ŸŽฏ Smart ID detection: Automatically appends Id suffix when passing UUIDs
  • ๐Ÿ”— Method chaining: All setters return $this
  • ๐Ÿ“ฆ Consistent API: Same interface across all factories
Factory Description
ProductFactory Create products with variants, prices, and stock
CustomerFactory Create customers with addresses and groups
OrderFactory Create orders with line items and states
CategoryFactory Create category trees
CartFactory Build carts with products and promotions
MediaFactory Create media files (images, documents)
PromotionFactory Create promotions and discounts
RuleFactory Create business rules
SalesChannelFactory Create sales channels
ShippingMethodFactory Create shipping methods
PaymentMethodFactory Create payment methods
TaxFactory Create tax configurations
B2B/ Organization, Employee, Quote, Role, etc.
Subscription/ SubscriptionPlan, SubscriptionInterval
MultiWarehouse/ Warehouse, WarehouseGroup
ReturnManagement/ OrderReturn

Examples:

// Standard fluent API
$product = (new ProductFactory($container))
    ->withName('Gaming Laptop')
    ->withPrice(1499.99)
    ->withStock(50)
    ->active()
    ->create();

// Magic methods - any property!
$product = (new ProductFactory($container))
    ->withName('Laptop')
    ->withEan('1234567890123')        // Magic: sets 'ean'
    ->withManufacturerNumber('MFG-01') // Magic: sets 'manufacturerNumber'
    ->create();

// Smart UUID handling - automatically adds 'Id' suffix
$order = (new OrderFactory($container))
    ->withCustomer($customerUuid)              // โ†’ sets 'customerId'
    ->withSalesChannel($salesChannelUuid)      // โ†’ sets 'salesChannelId'
    ->withPaymentMethod($paymentMethodUuid)    // โ†’ sets 'paymentMethodId'
    ->withOrderNumber('ORD-001')               // โ†’ sets 'orderNumber' (not UUID)
    ->create();

// B2B Example
$quote = (new B2B\QuoteFactory($container))
    ->withCustomer($uuid)           // โ†’ 'customerId'
    ->withOrganization($uuid)       // โ†’ 'organizationId'
    ->withExpirationDate($date)     // Magic method
    ->create();

๐Ÿ”ง Helpers (Execute Actions)

Helpers perform operations on existing entities. Use the HelperAccessor trait for easy access in tests.

Helper Description
OrderHelper Cancel orders, mark as paid/shipped, get order details
CartHelper Clear cart, remove items, recalculate
MediaHelper Assign media to products, delete media
StateManager Transition state machine states
CheckoutRunner Execute complete checkout flows
StorefrontRequestHelper Simulate storefront HTTP requests
MigrationDataTester Test data integrity in migrations
ConfigHelper Manage system configuration and feature flags
TimeHelper Time travel and date manipulation
ProductHelper Create and manage products
CustomerHelper Create and manage customers
SalesChannelHelper Create and manage sales channels
MailHelper Send and verify emails

Example:

use Algoritma\ShopwareTestUtils\Traits\HelperAccessor;

class MyTest extends AbstractIntegrationTestCase
{
    use HelperAccessor;

    public function testOrderFlow(): void
    {
        // Access helpers easily via trait methods
        $this->orderHelper()->markOrderAsPaid($orderId);
        $this->orderHelper()->markOrderAsShipped($orderId);

        // Set configuration
        $this->configHelper()->set('core.cart.maxQuantity', 100);

        // Time travel
        $this->timeHelper()->travelForward('30 days');
    }
}

โœจ Traits (Assertion Helpers)

Traits provide assertion methods for test verification. Note: Actions have been moved to Helper classes.

Trait Description
HelperAccessor Provides easy access to all Helper classes
DatabaseHelpers Database assertions (table exists, row count, etc.)
CacheHelpers Cache assertions (key exists, cache cleared)
TimeHelpers Time-related assertions (date in future/past, timestamp validity)
LogHelpers Log assertions (error logged, warning count, log contains)
MailHelpers Mail assertions (email sent, recipient correct)
EventHelpers Event assertions (event dispatched, payload validation)
QueueHelpers Queue assertions (job queued, queue empty)
MigrationHelpers Migration assertions (idempotency, schema changes)

Example:

use Algoritma\ShopwareTestUtils\Traits\HelperAccessor;
use Algoritma\ShopwareTestUtils\Traits\TimeHelpers;
use Algoritma\ShopwareTestUtils\Traits\MailHelpers;

class SubscriptionTest extends AbstractIntegrationTestCase
{
    use HelperAccessor;  // Access to all helpers
    use TimeHelpers;     // Time assertions
    use MailHelpers;     // Mail assertions

    public function testSubscriptionRenewal(): void
    {
        // Use TimeHelper for actions
        $this->timeHelper()->freezeTime(new \DateTime('2025-01-01 00:00:00'));

        // ... create subscription ...

        // Travel forward 30 days
        $this->timeHelper()->travelForward('30 days');

        // Run renewal process
        $this->runScheduledTask(RenewalTask::class);

        // Use MailHelpers trait for assertions
        $this->assertMailSent(1);
        $this->assertMailWasSent();
    }
}

๐Ÿงช Test Base Classes

AbstractIntegrationTestCase

Base class for integration tests (database, repositories, services).

Features:

  • Database transaction rollback after each test
  • Access to Shopware container and services
  • Event capturing
  • Mail capturing
  • Queue testing support
  • Custom assertions
  • Fixture Loading: Load fixtures with automatic dependency resolution and container injection
  • Reference Management: Retrieve entities created by fixtures anywhere in your test with getReference() and hasReference()

AbstractFunctionalTestCase

Base class for functional/storefront tests (HTTP requests, controllers).

Extends: AbstractIntegrationTestCase

Additional Features:

  • Storefront browser simulation
  • HTTP request/response testing
  • Session management

MigrationTestCase

Base class for migration tests.

Features:

  • Test migration up/down
  • Assert table creation/modification
  • Test data integrity
  • Assert migration idempotency

๐ŸŽจ Custom Assertions

The ShopwareAssertions trait provides Shopware-specific assertions:

// Entity assertions
$this->assertEntityExists('product', $productId);

// Price assertions
$this->assertPriceEquals(19.99, $product);

// Customer assertions
$this->assertCustomerHasRole($customer, 'B2B');

// Database assertions
$this->assertDatabaseHas('product', ['id' => $productId, 'active' => 1]);
$this->assertDatabaseMissing('order', ['id' => $deletedOrderId]);

// Order assertions
$this->assertOrderState($order, 'completed');

// Rule assertions
$this->assertRuleMatches($ruleId, $salesChannelContext);

// State machine assertions
$this->assertStateMachineState($orderId, 'order_state', 'completed');

// Table structure assertions
$this->assertTableExists('custom_table');
$this->assertColumnExists('product', 'custom_field');
$this->assertIndexExists('product', 'idx_custom');
$this->assertForeignKeyExists('order_line_item', 'fk_order_id');

// Mail Template assertions
$this->assertMailTemplateExists('order_confirmation');
$this->assertMailTemplateSubjectContains('order_confirmation', 'en-GB', 'Order confirmation');
$this->assertMailTemplateContentContains('order_confirmation', 'en-GB', 'Thank you for your order', true);

๐Ÿ—‚๏ธ Directory Structure

src/
โ”œโ”€โ”€ Assert/
โ”‚   โ””โ”€โ”€ ShopwareAssertions.php          # Custom assertions
โ”œโ”€โ”€ Core/
โ”‚   โ”œโ”€โ”€ AbstractIntegrationTestCase.php # Integration test base
โ”‚   โ”œโ”€โ”€ AbstractFunctionalTestCase.php  # Functional test base
โ”‚   โ””โ”€โ”€ MigrationTestCase.php           # Migration test base
โ”œโ”€โ”€ Factory/                             # Entity factories
โ”‚   โ”œโ”€โ”€ AbstractFactory.php             # Base factory with magic methods
โ”‚   โ”œโ”€โ”€ ProductFactory.php
โ”‚   โ”œโ”€โ”€ CustomerFactory.php
โ”‚   โ”œโ”€โ”€ OrderFactory.php
โ”‚   โ”œโ”€โ”€ CartFactory.php
โ”‚   โ”œโ”€โ”€ B2B/                            # B2B factories
โ”‚   โ”‚   โ”œโ”€โ”€ QuoteFactory.php
โ”‚   โ”‚   โ”œโ”€โ”€ OrganizationFactory.php
โ”‚   โ”‚   โ””โ”€โ”€ ...
โ”‚   โ”œโ”€โ”€ Subscription/                   # Subscription factories
โ”‚   โ”œโ”€โ”€ MultiWarehouse/                 # Multi-warehouse factories
โ”‚   โ””โ”€โ”€ ReturnManagement/               # Return management factories
โ”œโ”€โ”€ Helper/                              # Action helpers
โ”‚   โ”œโ”€โ”€ OrderHelper.php
โ”‚   โ”œโ”€โ”€ CartHelper.php
โ”‚   โ”œโ”€โ”€ MediaHelper.php
โ”‚   โ”œโ”€โ”€ StateManager.php
โ”‚   โ””โ”€โ”€ ...
โ”œโ”€โ”€ Traits/                              # Reusable behaviors
โ”‚   โ”œโ”€โ”€ HelperAccessor.php              # Access all helpers
โ”‚   โ”œโ”€โ”€ DatabaseHelpers.php
โ”‚   โ”œโ”€โ”€ CacheHelpers.php
โ”‚   โ”œโ”€โ”€ TimeHelpers.php
โ”‚   โ”œโ”€โ”€ EventHelpers.php
โ”‚   โ””โ”€โ”€ ...
โ””โ”€โ”€ Fixture/
    โ”œโ”€โ”€ FixtureInterface.php
    โ”œโ”€โ”€ FixtureManager.php
    โ”œโ”€โ”€ AbstractFixture.php
    โ””โ”€โ”€ ReferenceRepository.php

๐Ÿ” Advanced Examples

Testing with Fixtures

Fixtures allow you to load reusable test data with automatic dependency resolution. You can reference entities created by fixtures anywhere in your test while maintaining test isolation.

use Algoritma\ShopwareTestUtils\Fixture\AbstractFixture;
use Algoritma\ShopwareTestUtils\Fixture\ReferenceRepository;

class ProductFixture extends AbstractFixture
{
    public function load(ReferenceRepository $references): void
    {
        // Access container
        $factory = $this->getContainer()->get('product.repository');

        // Create a product
        $productId = Uuid::randomHex();
        $factory->create([
            ['id' => $productId, 'name' => 'Test Product', 'price' => [/* ... */]]
        ], $this->getContext());

        // Store reference for later use
        $references->set('main-product', $productId);
    }

    // Optional: Define dependencies
    public function getDependencies(): array
    {
        return [CategoryFixture::class];
    }
}

class MyTest extends AbstractIntegrationTestCase
{
    public function testWithFixture(): void
    {
        // Load fixtures
        $this->loadFixtures(new ProductFixture());

        // Retrieve reference anywhere in the test
        $productId = $this->getReference('main-product');

        // Check if reference exists
        if ($this->hasReference('optional-product')) {
            $optionalProductId = $this->getReference('optional-product');
        }

        // Use the entities
        $product = $this->getService('product.repository')->search(
            new Criteria([$productId]),
            $this->getContext()
        )->first();

        $this->assertNotNull($product);
    }
}

Key Features:

  • Reference Management: Store and retrieve entities by name using getReference() and hasReference()
  • Test Isolation: References are automatically cleared between tests via tearDown()
  • Dependency Resolution: Fixtures with dependencies are loaded in the correct order
  • Container Access: Fixtures have full access to the Shopware container

Testing with Time Travel

use Algoritma\ShopwareTestUtils\Traits\TimeHelpers;

class CouponExpirationTest extends AbstractIntegrationTestCase
{
    use TimeHelpers;

    public function testCouponExpires(): void
    {
        $coupon = $this->createCoupon(['validUntil' => '2025-12-31']);

        // Test before expiration
        $this->freezeTime(new \DateTime('2025-06-01'));
        $this->assertTrue($coupon->isValid());

        // Test after expiration
        $this->travelTo(new \DateTime('2026-01-01'));
        $this->assertFalse($coupon->isValid());
    }
}

Testing with Event Capture

use Algoritma\ShopwareTestUtils\Traits\EventHelpers;

class ProductEventTest extends AbstractIntegrationTestCase
{
    use EventHelpers;

    public function testProductCreationDispatchesEvent(): void
    {
        $this->startCapturingEvents();

        $product = (new ProductFactory($this->getContainer()))
            ->create();

        $this->assertEventWasDispatched(ProductCreatedEvent::class);
        $event = $this->getDispatchedEvent(ProductCreatedEvent::class);
        $this->assertEquals($product->getId(), $event->getProductId());
    }
}

Testing Migrations

use Algoritma\ShopwareTestUtils\Core\MigrationTestCase;
use Algoritma\ShopwareTestUtils\Traits\MigrationHelpers;

class Migration1234567890Test extends MigrationTestCase
{
    use MigrationHelpers;

    public function testMigrationCreatesTable(): void
    {
        // Test idempotency (can run multiple times)
        $this->assertMigrationIsIdempotent(Migration1234567890::class);

        // Test table creation
        $this->assertMigrationAddsTable(Migration1234567890::class, 'custom_entity');

        // Test column exists
        $this->assertColumnExists('custom_entity', 'custom_field');
    }

    public function testDataIntegrity(): void
    {
        // Seed old data
        $this->seedTable('old_table', [
            ['id' => 1, 'name' => 'Test']
        ]);

        // Run migration
        $this->runMigration(Migration1234567890::class);

        // Test data was migrated correctly
        $this->assertDatabaseHas('new_table', ['id' => 1, 'name' => 'Test']);
    }
}

Testing with Database Snapshots

use Algoritma\ShopwareTestUtils\Traits\DatabaseHelpers;

class BulkOperationTest extends AbstractIntegrationTestCase
{
    use DatabaseHelpers;

    public function testBulkImport(): void
    {
        // Create snapshot before bulk operation
        $snapshotId = $this->snapshotTable('product');

        // Perform bulk import
        $this->importProducts($csvFile);

        // Test the import
        $this->assertDatabaseHas('product', ['sku' => 'NEW-SKU-001']);

        // Restore original state for other tests
        $this->restoreTableSnapshot($snapshotId);
    }
}

๐Ÿ› ๏ธ Configuration

PHPUnit Configuration

<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/12.0/phpunit.xsd"
         bootstrap="vendor/autoload.php"
         colors="true">
    <testsuites>
        <testsuite name="Integration">
            <directory>tests/Integration</directory>
        </testsuite>
        <testsuite name="Functional">
            <directory>tests/Functional</directory>
        </testsuite>
    </testsuites>
</phpunit>

Running Tests

# Run all tests
vendor/bin/phpunit

# Run only integration tests
vendor/bin/phpunit --testsuite Integration

# Run with coverage
vendor/bin/phpunit --coverage-html coverage/

๐Ÿ“– Documentation

For detailed architecture documentation, see ARCHITECTURE.md.

๐Ÿงฉ PHPStorm IDE Support

Generating Factory Stubs and Metadata

This library provides IDE support for factory magic methods through auto-generated PHPStorm metadata and PHPStan stubs.

Generate Stubs

Run the following command to generate IDE autocomplete metadata:

composer generate-stubs

Or use the binary directly:

vendor/bin/generate-factory-stubs

This command generates two files:

  1. .phpstorm.meta.php - PHPStorm metadata for autocomplete support
  2. tests/factory-stubs.php - PHPStan stub file with DAL entity definitions

PHPStan Configuration

To enable PHPStan support, add the stub file to your phpstan.neon:

parameters:
    stubFiles:
        - tests/factory-stubs.php

This allows PHPStan to understand the factory magic methods and provide proper type analysis.

What It Does

The stub generator:

  • Uses ComposerPluginLoader to load Shopware plugins
  • Analyzes all DAL entity definitions via DalMetadataService
  • Generates type hints for factory magic methods (with*() and set*())
  • Enables full IDE autocomplete for entity properties in factories

IDE Integration

After generation, PHPStorm will provide:

  • Autocomplete for all entity properties in factory methods
  • Type checking for method parameters
  • Quick navigation to property definitions

Example:

// PHPStorm will autocomplete these magic methods
$product = (new ProductFactory($container))
    ->withName('Test')           // โœ“ Autocomplete available
    ->withPrice(99.99)           // โœ“ Type checking enabled
    ->withManufacturerNumber('') // โœ“ Navigation to definition
    ->create();

When to Regenerate

Run composer generate-stubs after:

  • Adding new entity definitions
  • Installing/updating Shopware plugins
  • Modifying entity properties
  • Installing this library for the first time

๐Ÿค Contributing

Contributions are welcome! Please follow these guidelines:

  1. Factories should only create entities
  2. Helpers should only perform actions
  3. Traits should provide reusable behaviors
  4. All code must follow PSR-12 coding standards
  5. Add tests for new features

Code Quality Tools

# Check code style
composer cs-check

# Fix code style
composer cs-fix

# Run static analysis
composer phpstan

# Run rector
composer rector-check

๐Ÿ“„ License

This project is licensed under the MIT License.

๐Ÿ™ Credits

Developed by Algoritma for the Shopware community.

๐Ÿ’ก Support

For issues, questions, or feature requests, please open an issue on GitHub.