algoritma / shopware-test-utils
A collection of helpers for Shopware 6 integration/functional tests.
Installs: 191
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/algoritma/shopware-test-utils
Requires
- php: >=8.2
- ext-pdo: *
- fakerphp/faker: ^1.24
- roave/better-reflection: ^6.0
- shopware/core: ^6.7
- symfony/finder: ^7.0|^8.0
- symfony/process: ^7.0|^8.0
Requires (Dev)
- algoritma/castor-recipes: dev-main
- algoritma/php-coding-standards: ^3.0
- frosh/shopware-rector: ^0.5.8
- phpstan/phpstan-phpunit: ^2.0
- phpunit/phpunit: ^11.5
- symfony/browser-kit: ^7.0|^8.0
Suggests
- store.shopware.com/swagcommercial: Enables the B2B extension for functional tests.
This package is auto-updated.
Last update: 2026-02-02 16:19:14 UTC
README
A comprehensive collection of helpers, factories, and utilities for Shopware 6 integration and functional tests.
๐ฆ Installation
Prerequisites
- PHP >= 8.2
- Shopware 6.7
- A Shopware project with a working DB configuration (the stub generator boots the kernel)
Install in your Shopware project
composer require --dev algoritma/shopware-test-utils
If you plan to use the B2B factories/helpers, install Shopware Commercial (store.shopware.com/swagcommercial)
and make sure the plugin is active (license required).
๐ 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 trait helper $order = $this->orderPlace($cart, $context); // Assert order was created $this->orderAssertState($order, 'open'); $this->assertDatabaseHas('order', ['id' => $order->getId()]); } }
Short trait usage:
// Cart + checkout helpers via traits $cart = $this->createCart($context) ->withProduct($product->getId()) ->create(); $this->cartAssertContainsProduct($cart, $product->getId()); $order = $this->checkoutPlaceOrder($cart, $context);
Functional/Storefront Test
<?php use Algoritma\ShopwareTestUtils\Core\AbstractFunctionalTestCase; class StorefrontCheckoutTest extends AbstractFunctionalTestCase { public function testCheckoutFlow(): void { // Simulate user actions $this->storefrontAddToCart($productId); $this->storefrontProceedToCheckout(); $this->storefrontSubmitOrder(); $this->storefrontAssertResponseRedirects($this->storefrontBrowser()->getResponse()); } }
โก Parallel Tests (Paratest)
When TEST_TOKEN is present (Paratest worker), the suite switches to a per-worker database suffix (_p{token}).
If the database does not exist, it is created and initialized once with:
bin/ci system:install --drop-database --basic-setup --force --no-assign-themebin/console dal:refresh:index --only category.indexer --no-interaction
Custom setup commands can be injected via:
SW_TEST_INSTALL_COMMANDS(separate commands with newline or;)- or programmatically:
ParallelTestBootstrapper::setInstallCommands()/addInstallCommand()
To ensure CLI commands like bin/console pick the correct worker DB, set TEST_TOKEN in .env.test
and include it in DATABASE_URL, for example:
DATABASE_URL=mysql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}${TEST_TOKEN:-}?serverVersion=8.4
Use a default like TEST_TOKEN=local to avoid a trailing underscore in non-parallel runs.
Note: The per-worker bootstrap is triggered in AbstractIntegrationTestCase::setUpBeforeClass().
If your tests do not extend AbstractIntegrationTestCase/AbstractFunctionalTestCase, call
ParallelTestBootstrapper::ensureParallelBootstrap() in your own base class.
๐ 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*()andset*()for any property - ๐ฏ Smart ID detection: Automatically appends
Idsuffix 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();
Custom factories (your project)
Create project-specific factories by extending AbstractFactory and placing them in a namespace that is
autoloaded (ideally under autoload-dev so the stub generator can discover them).
<?php namespace Acme\Shopware\Tests\Factory; use Algoritma\ShopwareTestUtils\Factory\AbstractFactory; use Shopware\Core\Framework\Uuid\Uuid; use Symfony\Component\DependencyInjection\ContainerInterface; use Acme\Shopware\Core\Content\Badge\BadgeDefinition; class BadgeFactory extends AbstractFactory { public function __construct(ContainerInterface $container) { parent::__construct($container); $this->data = [ 'id' => Uuid::randomHex(), 'name' => 'Gold', 'priority' => 10, 'active' => true, ]; } public function withPriority(int $priority): self { $this->data['priority'] = $priority; return $this; } protected function getRepositoryName(): string { return 'acme_badge.repository'; } protected function getEntityName(): string { return BadgeDefinition::ENTITY_NAME; } }
Notes:
- You can still rely on magic
with*()/set*()methods for simple fields. - For custom entities, ensure the plugin is installed/active so DAL definitions are available to the stub generator.
- Make sure your
composer.jsonincludes the namespace inautoload-devand runcomposer dump-autoload.
Example composer.json snippet:
{
"autoload-dev": {
"psr-4": {
"Acme\\Shopware\\Tests\\": "tests/"
}
}
}
๐ง 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\HelperAccessorTrait; class MyTest extends AbstractIntegrationTestCase { use HelperAccessorTrait; 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.) |
CacheTrait |
Cache assertions (key exists, cache cleared) |
ContextTrait |
Context management (create default context, sales channel context) |
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\HelperAccessorTrait; use Algoritma\ShopwareTestUtils\Traits\TimeTrait; use Algoritma\ShopwareTestUtils\Traits\MailTrait; class SubscriptionTest extends AbstractIntegrationTestCase { use HelperAccessorTrait; // Access to all helpers use TimeTrait; // Time assertions use MailTrait; // 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 MailTrait 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()andhasReference()
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
โ โโโ CacheTrait.php
โ โโโ ContextTrait.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()andhasReference() - 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\TimeTrait; class CouponExpirationTest extends AbstractIntegrationTestCase { use TimeTrait; 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\EventTrait; class ProductEventTest extends AbstractIntegrationTestCase { use EventTrait; 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\MigrationTrait; class Migration1234567890Test extends MigrationTestCase { use MigrationTrait; 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\DatabaseTrait; class BulkOperationTest extends AbstractIntegrationTestCase { use DatabaseTrait; 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
Environment
- Configure
DATABASE_URLin your Shopware project as usual (tests and stub generation boot the kernel). - If you rely on
.env.test, run commands withAPP_ENV=testor--env=test. - For parallel testing, see the Parallel Tests section above.
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>
PHPStan Stubs
After generating stubs, add the file to your phpstan.neon:
parameters: stubFiles: - tests/factory-stubs.php
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.
๐งฉ IDE and PHPStan Support
Generate Factory Stubs (PHPStan + PhpStorm)
The stub generator boots the Shopware kernel and scans:
- Built-in factories in
src/Factory - Any custom factories found in your project's
autoload-devpaths
Run it from your Shopware project root:
composer generate-stubs -- --env=test
Or call the binary directly:
vendor/bin/generate-factory-stubs --env=test
Default output:
tests/factory-stubs.php(PHPStan stub file)tests/.phpstorm.meta.php(PhpStorm metadata)
You can change the output directory:
vendor/bin/generate-factory-stubs --env=test --output-dir=var/stubs
PhpStorm Integration
Add the generated metadata to your project root (copy or include):
<?php if (file_exists(__DIR__ . '/tests/.phpstorm.meta.php')) { require __DIR__ . '/tests/.phpstorm.meta.php'; }
Import live templates from phpstorm-live-templates.xml (Settings > Editor > Live Templates > + > Import).
What It Does
The stub generator:
- Uses
ComposerPluginLoaderto load Shopware plugins - Analyzes DAL entity definitions via
DalMetadataService - Generates type hints for factory magic methods (
with*()andset*()) - Enables autocomplete for entity properties in factories
When to Regenerate
Run composer generate-stubs after:
- Adding or changing custom factories
- Installing or updating Shopware plugins
- Modifying entity definitions
๐ค Contributing
Contributions are welcome! Please follow these guidelines:
- Factories should only create entities
- Helpers should only perform actions
- Traits should provide reusable behaviors
- All code must follow PSR-12 coding standards
- 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.