Installation:
composer require --dev liip/test-fixtures-bundle
Add the bundle to config/bundles.php (Symfony 5.1+):
Liip\TestFixturesBundle\LiipTestFixturesBundle::class => ['test' => true],
First Use Case:
Extend Liip\TestFixturesBundle\Test\FixturesTrait in your test class:
use Liip\TestFixturesBundle\Test\FixturesTrait;
use Liip\TestFixturesBundle\Services\Database\DatabaseTestCase;
class MyTest extends DatabaseTestCase
{
use FixturesTrait;
protected function getDatabaseName(): string
{
return 'default'; // or your custom connection name
}
public function testSomething()
{
$this->loadFixtures([MyUserFixture::class]);
// Test logic here
}
}
Key Files to Review:
doc/database.md (core usage)config/packages/test/liip_test_fixtures.yaml (default config)src/Services/FixtureLoader.php (understanding fixture loading)Fixture Organization:
Group fixtures by feature/module (e.g., UserFixtures, ProductFixtures).
Use namespaces to logically separate concerns:
// src/DataFixtures/UserFixtures.php
namespace App\DataFixtures;
class UserFixtures extends FixtureGroup
{
public function load(ObjectManager $manager)
{
$this->addFixture(new UserFixture());
$this->addFixture(new RoleFixture());
}
}
Test Structure:
class UserTest extends DatabaseTestCase
{
use FixturesTrait;
protected function setUp(): void
{
$this->loadFixtures([
UserFixtures::class,
ProductFixtures::class // Shared fixtures
]);
parent::setUp();
}
public function testUserCreation()
{
// Test logic
}
}
Database Isolation:
getDatabaseName() to target specific connections.getDatabaseConfig() for custom configurations:
protected function getDatabaseConfig(): array
{
return [
'memory' => true, // In-memory DB for speed
'purge' => true, // Auto-purge between tests
];
}
Partial Loading: Load only specific fixtures per test method:
public function testAdminFeatures()
{
$this->loadFixtures([AdminUserFixture::class]);
// Test admin-specific logic
}
Symfony Events:
Listen to liip_test_fixtures.load.fixtures to modify fixtures dynamically:
public static function getSubscribedEvents()
{
return [
'liip_test_fixtures.load.fixtures' => 'onLoadFixtures',
];
}
public function onLoadFixtures(LoadFixturesEvent $event)
{
$event->addFixture(new DynamicFixture());
}
Custom Fixture Classes:
Extend Liip\TestFixturesBundle\Services\FixtureLoader for custom loading logic:
class CustomLoader extends FixtureLoader
{
protected function loadFixtures(array $fixtures): void
{
// Custom logic
parent::loadFixtures($fixtures);
}
}
Performance:
memory: true in config for fast in-memory tests.cache: true (requires doctrine/doctrine-cache-bundle).Fixture Dependencies:
orderBy in FixtureGroup):
$this->addFixture(new RoleFixture())->orderBy(10);
$this->addFixture(new UserFixture())->orderBy(20);
ForeignKeyConstraintViolationException if dependencies are misordered.Database State:
purge: true is set.purgeDatabase() between tests:
public function testA()
{
$this->loadFixtures([...]);
// ...
}
public function testB()
{
$this->purgeDatabase(); // Reset state
$this->loadFixtures([...]);
}
Configuration Overrides:
config/packages/test/liip_test_fixtures.yaml may be overridden by environment variables.%env(resolve: DATABASE_URL)% for dynamic configs.Doctrine Events:
prePersist). Disable with:
# config/packages/test/doctrine.yaml
doctrine:
orm:
event_listeners:
# Disable default listeners
Fixture Loading Logs:
Enable verbose logging in config/packages/test/liip_test_fixtures.yaml:
liip_test_fixtures:
debug: true
Database Dumps:
Use getDatabaseConnection()->getSchemaManager()->createSchemaSql() to inspect schema:
public function testSchema()
{
$sql = $this->getDatabaseConnection()->getSchemaManager()->createSchemaSql(
$this->getDatabaseConnection()->getSchemaManager()->createSchema()
);
file_put_contents('schema.sql', $sql);
}
Fixture Validation:
assertDatabaseHas() from database-testing trait:
$this->assertDatabaseHas('users', ['email' => 'test@example.com']);
Custom Fixture Types:
Extend Liip\TestFixturesBundle\Services\FixtureInterface for non-Doctrine fixtures (e.g., Elasticsearch):
class ElasticFixture implements FixtureInterface
{
public function load(ObjectManager $manager): void
{
// Custom logic
}
}
Hooks:
Override onPreLoadFixtures() and onPostLoadFixtures() in your test class:
protected function onPreLoadFixtures(): void
{
// Pre-load logic (e.g., seed config)
}
protected function onPostLoadFixtures(): void
{
// Post-load logic (e.g., refresh cache)
}
Parallel Testing:
getDatabaseName() to generate unique DB names for parallel runs:
protected function getDatabaseName(): string
{
return 'test_' . $this->getTestClassName();
}
Memory vs. SQLite:
memory: true uses SQLite in-memory (fast but no persistence).sqlite_memory: true (alternative) may behave differently in some PHP versions.Purger Strategies:
purge: true drops and recreates the schema.Liip\TestFixturesBundle\Services\Database\Purger\Purger directly:
$this->purgeDatabase(['users', 'roles']); // Purge specific tables
Environment Variables:
DATABASE_URL in .env.test overrides bundle config.LIIP_TEST_FIXTURES_PURGE to toggle purging via env:
purge: '%env(bool:LIIP_TEST_FIXTURES_PURGE,true)%'
Doctrine Migrations:
setUp():
protected function setUp(): void
{
$this->runMigrations();
$this->loadFixtures([...]);
}
doctrine/doctrine-migrations-bundle and:
liip_test_fixtures:
run_migrations: true
How can I help you explore Laravel packages today?