---
title: Integration Testing With the Shopware Test Kernel | Shopware Community Hub
description: >-
  Learn how to write reliable integration tests by booting the Shopware test
  kernel to verify persistence logic, service wiring, and application behavior.
canonical_url: 'https://hub.shopware.com/learn/unit/integration-testing-with-kernel'
---

# Integration Testing With the Shopware Test Kernel

<LearningObjectives>

- Boot the Shopware test kernel and access services from the test container.
- Use database fixtures and repositories safely in integration tests.
- Test service wiring, handlers, and persistence logic in a full application context.
- Apply Shopware test behavior traits to reduce boilerplate and ensure isolation.
- Understand when integration tests are appropriate and when unit tests are sufficient.
  
</LearningObjectives>

# Integration Testing With the Shopware Test Kernel

This learning unit introduces integration testing in Shopware. You'll learn when it makes sense to go beyond unit tests, how to boot the Shopware test kernel, and how to write reliable tests that interact with repositories and the database while keeping tests isolated and repeatable.

## When to Write Integration Tests

Unit tests are ideal for isolated logic. However, some scenarios require integration tests to verify behavior **across multiple components**.

**Use integration tests when:**

- **Persistence logic:** Repository interactions, entity lifecycle events, or database constraints.
- **Service wiring:** Verifying that services are correctly registered and can be fetched from the container.
- **Handlers:** Command handlers, event subscribers, or message queue handlers that rely on the full application context.

**Avoid integration tests for:**

- **Simple Logic:** Business logic that can be tested in isolation without database or service dependencies. This also applies to complex calculations that technically use the database but can be mocked easily.

- **Redundant tests:** Do not duplicate the same logic in both unit and integration tests. Use unit tests for the logic itself and integration tests to verify the wiring between components.

- **Performance-critical paths:** Integration tests are slower due to kernel booting and database access.

- **External Services:** Third-party integrations should be mocked or stubbed to keep tests fast and reliable.

## Booting the Shopware Test Kernel and Accessing Services

Integration tests in Shopware use the `KernelTestBehaviour` trait to boot the Symfony kernel. This provides access to the **full test service container** and **application context**.

Conceptually, this works very similarly to Symfony's testing approach for [booting the kernel](https://symfony.com/doc/current/testing.html#booting-the-kernel), and [access services](https://symfony.com/doc/current/testing.html#retrieving-services-in-the-test).

The main difference is that `KernelTestBehavior` provides a convenient wrapper that ensures:

- The kernel is booted correctly for tests.
- The **test service container** is used.
- All services (including private ones) are accessible.

```php
trait KernelTestBehaviour
{
    use EventDispatcherBehaviour;

    protected static function getKernel(): Kernel
    {
        return KernelLifecycleManager::getKernel();
    }

    /**
     * This results in the test container, with all private services public
     */
    protected static function getContainer(): ContainerInterface
    {
        $container = static::getKernel()->getContainer();

        if (!$container->has('test.service_container')) {
            throw new \RuntimeException('Unable to run tests against kernel without test.service_container');
        }

        /** @var ContainerInterface $testContainer */
        $testContainer = $container->get('test.service_container');

        return $testContainer;
    }
}
```

### Supporting Test Behavior Traits

Shopware provides a rich set of additional traits to simplify integration testing, including:

- `DatabaseTransactionBehaviour`
- `CacheTestBehaviour`
- `BasicTestDataBehaviour`
- and more

All of them live in the `Shopware\Core\Test\Framework\TestCaseBase` namespace. These traits help you:

- Reduce boilerplate code
- Prepare valid test data easily
- Clean up state automatically before and after tests
- Keep integration tests reliable and isolated

They rely on Shopware's repository pattern and the Symfony dependency injection container to fetch data from the database.

It simplifies access to commonly used IDs and entities such as payment methods, shipping methods, salutations, countries, categories, taxes, document types, and state machine states.

### Basic Usage Pattern

To write integration tests in `tests/integration` for the `MyAwesomeService`, follow these steps:

- Use the `KernelTestBehaviour` trait in your test class. This provides access to the Shopware test kernel and the service container.

- Boot the kernel by calling `static::getContainer()`. This ensures the kernel is started and the container is available.

- Access your service using `static::getContainer()->get(MyAwesomeService::class)`.

**Example:**

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

namespace Shopware\Tests\Integration\Core\YourNamespace;

use PHPUnit\Framework\TestCase;
use Shopware\Core\Test\KernelTestBehaviour;
use Shopware\Core\Framework\YourNamespace\MyAwesomeService;

class MyAwesomeServiceTest extends TestCase
{
    use KernelTestBehaviour;

    public function testServiceAccess(): void
    {
        $service = static::getContainer()->get(MyAwesomeService::class);

        static::assertInstanceOf(MyAwesomeService::class, $service);

        $result = $service->createAwesomeThings(5);

        static::assertIsArray($result);
        static::assertCount(5, $result);
    }
}
```

### Preparing State with Repositories and Database Access

You can also access the repositories and the database connection, which can be helpful to prepare a certain state.

```php
class MyAwesomeServiceTest extends TestCase
{
    use KernelTestBehaviour;

    public function testWithPreparedState(): void
    {
        $container = static::getContainer();
        /** @var EntityRepository $productRepository */
        $productRepository = $container->get('product.repository');
        $connection = $container->get(Connection::class);

        $id = Uuid::randomHex();
        $productRepository->create([
            [
              'id' => $id,
              'name' => 'Test product',
              'productNumber' => 'P123',
              'stock' => 10,
              'price' => [
                [
                    'currencyId' => Uuid::randomHex(), 
                    'gross' => 10, 
                    'net' => 8, 
                    'linked' => false
                ]
              ],
              'tax' => ['name' => 'Standard', 'taxRate' => 19],
            ]
        ], $container->get('shopware.storefront.context.default'));
    }
}
```

Beyond preparing a state, in some cases, writing custom assertions using SQL can be useful.

**Example:**

```php
    $count = $connection->fetchOne(
        'SELECT COUNT(*) FROM product WHERE id = :id',
        ['id' => Uuid::fromHexToBytes($id)]
    );
    static::assertSame(1, (int) $count);
```

However, this approach should be used sparingly. Direct SQL assertions:

- Increase test complexity
- Make tests harder to read
- Significantly raise the risk of flaky tests

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

A flaky test is one that sometimes passes and sometimes fails with the same codebase.

This inconsistency depends on external factors, such as timing, randomness of other tests, state leakage, network instability and so on.

Flakiness has an outsized impact: it breaks trust. Teams stop believing the pipeline, or in doing tests at all.

</Callout>

#### Avoid Flaky Tests

A common example of a flaky test is asserting on a count of records, while other tests are inserting or deleting records in the same table, and no filter is applied to isolate the records created by your test.

It is better to rely on the repository object existing mechanisms, such as `search()` when possible.

**Example:**

```php
$criteria = new Criteria([$id]);

$product = $this->productRepository->search($criteria, Context::createDefaultContext())->first();
static::assertInstanceOf(ProductEntity::class, $product);
```

## How to Use Database Fixtures and Repositories Safely

Always use the **test context** (`static::getContainer()->get(Context::class))` when working with repositories in integration tests. This ensures that test data never affects production data.

Also, use the following guidelines to ensure your tests are reliable and repeatable:

- **Always use repositories:** Use repositories (e.g., `$productRepository = static::getContainer()->get('product.repository')`) for creating, updating, or deleting entities. This ensures all business logic, validations, and events are executed.

- **Keep test data isolated:** Use unique IDs to avoid conflicts and clean up test data after each test if needed.

- **Prefer transactions or versioning**: Use transactions or Shopware’s versioning if you need to rollback changes after a test.

- **Avoid direct SQL when possible:** Direct SQL should be the exception. Prefer repository methods for safety, consistency, and maintainability.

**Example:**

```php
public function testProductFixture(): void
{
    $productRepository = static::getContainer()->get('product.repository');
    $context = $this->context;

    $productId = Uuid::randomHex();
    $productData = [
        'id' => $productId,
        'name' => 'Test Product',
        'productNumber' => 'TEST-123',
        'stock' => 10,
        'price' => [
            [
                'currencyId' => Defaults::CURRENCY, 
                'gross' => 10, 
                'net' => 8, 
                'linked' => false
            ]
        ],
        'tax' => ['name' => 'Standard', 'taxRate' => 19],
    ];

    $productRepository->create([$productData], $context);

    $criteria = new Criteria([$productId]);
    $product = $productRepository->search($criteria, $context)->get($productId);

    static::assertNotNull($product);

    // Clean up
    $productRepository->delete([['id' => $productId]], $context);
}
```

### Using the `DatabaseTransactionBehaviour`

As an alternative to manually cleaning up test data (writing deletion of record), Shopware provides the `DatabaseTransactionBehaviour` trait for integration tests. This trait ensures that each test runs inside a database transaction.

Be careful when combining it with other traits or with manual transaction handling (typically inside `setUp` / `tearDown` methods). Nested transactions can easily lead to unexpected behavior.

#### Transaction Nesting and Why It Is Dangerous

A nesting issue occurs when multiple layers or components try to manage the same resource (like database transactions) independently, creating conflicting or overlapping control structures.

When one transaction is started inside another transaction that's already active, you get nested transactions. This causes:

- **State conflicts:** The inner transaction's `commit()` or `rollback()` may not behave as expected because it's inside an outer transaction.
- **Unpredictable behavior:** Operations may not be isolated as intended.
- **Resource management issues:** It is unclear which layer is responsible for committing or rolling back.
- **Harder error handling:** Exceptions in nested transactions can lead to confusion about which transaction to roll back.
- **Inconsistent database state:** As the database may end up in an inconsistent state when errors occur, any other tests may be affected.

### Explanations of `DatabaseTransactionBehaviour`

**Automatic Transaction Handling:** Before each test, a transaction is started. After the test, the transaction is rolled back, so database changes are not persisted.
**Test Isolation:** Each test runs with a clean database state, preventing side effects between tests.
**Error Detection:** If a transaction is not properly closed or the nesting level is wrong, the trait throws an exception to alert you.

**Usage**

Add `use DatabaseTransactionBehaviour;` to your test class. The trait requires `static::getContainer()` to access the database connection.

When used correctly, your integration tests remain reliable and repeatable without a manual cleanup. Avoid manual transaction handling in tests using this trait to prevent conflicts, especially in `setUp` / `tearDown` methods.

## Test Service Wiring

Integration tests are also used to verify that services are correctly registered in the container.

Use `static::getContainer()->get(MyService::class)` to retrieve your service and `static::assertInstanceOf(MyService::class, $service)` to verify its type.

## Test Handlers (Events and Messages)

Handlers (such as command handlers, event handlers, or message handlers) are ideal candidates for integration tests.

They rely on the full application context and are typically triggered by events, commands, and messages.

In integration tests, handlers can be tested by triggering their logic through the service or dispatching the relevant event or command.

Use the container to fetch the handler or dispatcher, then call the handler method or dispatch the event and assert the expected outcome.

For events, the `EventDispatcherBehaviour` trait helps test event dispatching in integration tests by allowing you to register temporary event listeners and automatically clean them up.

**Example:**

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

namespace Shopware\Tests\Integration\Core\YourNamespace;

use PHPUnit\Framework\TestCase;
use Shopware\Core\Framework\Test\TestCaseBase\EventDispatcherBehaviour;
use Shopware\Core\Framework\Test\TestCaseBase\KernelTestBehaviour;
use Shopware\Core\Content\Product\Events\ProductWrittenEvent;

class ProductEventTest extends TestCase
{
    use KernelTestBehaviour;
    use EventDispatcherBehaviour;

    public function testProductCreationTriggersEvent(): void
    {
        $dispatcher = static::getContainer()->get('event_dispatcher');
        $eventCalled = false;

        // Register a temporary listener
        $this->addEventListener(
            $dispatcher,
            ProductWrittenEvent::class,
            function (ProductWrittenEvent $event) use (&$eventCalled): void {
                $eventCalled = true;
                static::assertNotEmpty($event->getIds());
            }
        );

        // Trigger the event (e.g., by creating a product)
        $productRepository = static::getContainer()->get('product.repository');
        $productRepository->create([
            [
                'id' => Uuid::randomHex(),
                'name' => 'Test Product',
                'productNumber' => 'TEST-123',
                'stock' => 10,
                'price' => [['currencyId' => Defaults::CURRENCY, 'gross' => 10, 'net' => 8, 'linked' => false]],
                'tax' => ['name' => 'Standard', 'taxRate' => 19],
            ]
        ], Context::createDefaultContext());

        // Assert the event was triggered
        static::assertTrue($eventCalled);
    }
}

```

### Testing Messages Handlers

`QueueTestBehaviour` helps test message queue handlers by clearing the queue and running a worker in your integration test.

Here’s an example integration test that dispatches a message, runs the worker, and asserts the handler’s effect:

1. The test uses `QueueTestBehaviour` to manage the queue.

2. It dispatches a message to the bus.

3. It runs the worker to process the message.

4. It asserts the expected outcome (e.g., a database change or a method call).

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

namespace Shopware\Tests\Integration\Core\YourNamespace;

use PHPUnit\Framework\TestCase;
use Shopware\Core\Framework\Test\TestCaseBase\QueueTestBehaviour;
use Symfony\Component\Messenger\MessageBusInterface;
use Shopware\Core\Framework\MessageQueue\ExampleMessage;
use Doctrine\DBAL\Connection;

class ExampleHandlerTest extends TestCase
{
    use QueueTestBehaviour;

    public function testHandlerIsTriggered(): void
    {
        $bus = static::getContainer()->get('messenger.bus.test_shopware');
        static::assertInstanceOf(MessageBusInterface::class, $bus);

        // Dispatch a message
        $bus->dispatch(new ExampleMessage('test-payload'));

        // Run the worker to process the message
        $this->runWorker();

        // Assert the expected outcome, e.g., a row in the database
        $connection = static::getContainer()->get(Connection::class);
        $result = $connection->fetchOne('SELECT COUNT(*) FROM example_table WHERE payload = :payload', [
            'payload' => 'test-payload',
        ]);
        static::assertSame(1, (int) $result);
    }
}
```

## Test Persistence Logic

To test persistence, use repositories to create, update, or delete entities.

After performing an operation, use the repository or direct database queries to verify the data was persisted as expected. Use unique IDs and clean up after tests, or use traits to ensure changes are rolled back.

Persistence logic often depends on multiple application behaviors, such as:

```text
- Cache
- Session
- Database
- …
```

Shopware provides dedicated test traits to initialize these states, clean them up, or disable them when necessary:

- `CacheTestBehaviour`
- `SessionTestBehaviour`
- `DatabaseTransactionBehaviour`
- …

In Shopware integration tests, it is common to rely on a combination of these traits. They are often grouped together in `IntegrationTestBehaviour.php`.

At first glance, this may seem like overkill. However, these traits encapsulate important behaviors and ensure proper test isolation by resetting states between tests.

```php
trait IntegrationTestBehaviour
{
    use BasicTestDataBehaviour;
    use CacheTestBehaviour;
    use DatabaseTransactionBehaviour;
    use FilesystemBehaviour;
    use KernelTestBehaviour;
    use RequestStackTestBehaviour;
    use SessionTestBehaviour;
    use TranslationTestBehaviour;
}
```

Using these traits allows you to focus on the persistence logic itself rather than on manually managing test states and cleanup. Let's see the benefits of using these traits.

### Key Features per Traits

It is always good to have a brief overview of what each trait does. In the following, you will find a list of the most important traits and their features:

| Trait                          | Behaviors                                                                                                                     |
|--------------------------------|-------------------------------------------------------------------------------------------------------------------------------|
| `BasicTestDataBehaviour`       | Methods to get valid or available IDs for payment, shipping, country, category, tax, document type, and salutation entities.  |
|                                | Methods to retrieve specific entities, such as available payment or shipping methods.                                         |
|                                | Utility functions for fetching language and locale IDs.                                                                       |
| `CacheTestBehaviour`           | Provides methods to access and manipulate cache pools via the service container.                                              |
|                                | Ensures cache-related side effects do not impact other tests, supporting test isolation.                                      |
|                                | Useful for testing cache invalidation, cache warmers, and cache-dependent logic.                                              |
| `DatabaseTransactionBehaviour` | Starts a database transaction before each test and rolls it back after, ensuring no data persists.                            |
|                                | Detects transaction nesting issues and throws exceptions if the transaction state is inconsistent.                            |
|                                | Reduces the need for manual cleanup of test data (and relies on the database supporting transactions).                        |
| `FilesystemBehaviour`          | Offers methods to create, copy, and remove files or directories during tests.                                                 |
|                                | Ensures test files and directories are cleaned up after tests                                                                 |
|                                | Useful for testing file uploads, exports, imports, and other file-related logic.                                              |
| `KernelTestBehaviour`          | Boots the Symfony kernel for each test, ensuring a fresh application state.                                                   |
|                                | Provides access to the service container via `static::getContainer()`                                                         |
|                                | Supports reloading or rebooting the kernel as needed during tests.                                                            |
| `RequestStackTestBehaviour`    | Provides methods to push, pop, or replace requests in the stack.                                                              |
|                                | Useful for simulating different request scenarios (e.g., different headers, methods, or paths).                               |
| `SessionTestBehaviour`         | Provides methods to start, clear, or modify the session state.                                                                |
|                                | Useful for testing session-dependent logic, such as authentication or user preferences.                                       |
| `TranslationTestBehaviour`     | Provides methods to load, override, or clear translations for different locales.                                              |
|                                | Useful for verifying multilingual behavior and translation fallbacks.                                                         |

## Summary

In this learning unit, you learned:

- When to **use integration tests**
  - Persistence logic and repository interactions
  - Service wiring verification
  - Command/event handlers need full application context

- When to **avoid integration tests**
  - Simple isolated business logic
  - Complex calculations, redundant with unit tests
  - Performance-critical paths

- How to use `KernelTestBehaviour` to boot Symfony kernel
- How to access services/repositories: `static::getContainer()->get(...)`
- How helper traits can reduce boilerplate when used intentionally
- Why proper state cleanup is essential for reliable integration tests

With this knowledge, you have a solid foundation to start writing integration tests for your Shopware plugins.
