matthiasnoback/doctrine-orm-test-service-provider
PHPUnit service provider for testing Doctrine ORM with a test service container. Adds a per-test SQLite connection, builds schema automatically from your entity directories, and exposes EntityManager, EventManager, and Connection for fast unit/integration tests.
Install Dependencies:
composer require matthiasnoback/phpunit-test-service-container
composer require matthiasnoback/doctrine-orm-test-service-provider
Configure PHPUnit:
Add the service container to your phpunit.xml:
<phpunit>
<extensions>
<extension class="Noback\PHPUnitTestServiceContainer\PHPUnit\ServiceContainerExtension"/>
</extensions>
</phpunit>
First Test Case:
Extend your test class with TestCaseWithEntityManager and implement getEntityDirectories():
use PHPUnit\Framework\TestCase;
use Noback\PHPUnitTestServiceContainer\PHPUnit\TestCaseWithEntityManager;
class UserTest extends TestCase
{
use TestCaseWithEntityManager;
protected function getEntityDirectories(): array
{
return [__DIR__ . '/../src/Entity'];
}
public function testUserPersistence()
{
$user = new User();
$user->setName('Test User');
$this->getEntityManager()->persist($user);
$this->getEntityManager()->flush();
$this->assertEquals(1, $this->getEntityManager()->getRepository(User::class)->count([]));
}
}
EntityManager or database connections manually.Test Class Structure:
class RepositoryTest extends TestCase
{
use TestCaseWithEntityManager;
protected function getEntityDirectories(): array
{
return [
__DIR__ . '/../src/Entity/User',
__DIR__ . '/../src/Entity/Product',
];
}
public function testFindByName()
{
$repo = $this->getEntityManager()->getRepository(User::class);
// Test logic...
}
}
Dependency Injection:
Inject the EntityManager into your subject-under-test (e.g., a repository or service):
public function testUserService()
{
$service = new UserService($this->getEntityManager());
$result = $service->findById(1);
$this->assertInstanceOf(User::class, $result);
}
Event Listeners/Subscribers: Register listeners dynamically:
public function setUp(): void
{
$eventManager = $this->getEventManager();
$eventManager->addEventListener(
'prePersist',
new AuditListener()
);
}
Database Connection:
Access the underlying Connection for raw queries:
public function testRawQuery()
{
$connection = $this->getConnection();
$result = $connection->fetchAll('SELECT * FROM user');
$this->assertCount(1, $result);
}
Shared Fixtures:
Use setUp() to create reusable entities:
public function setUp(): void
{
$user = new User();
$user->setName('Admin');
$this->getEntityManager()->persist($user);
$this->getEntityManager()->flush();
}
Transactions: Leverage Doctrine’s transactional tests (enabled by default) for rollbacks:
public function testTransactionalRollback()
{
$user = new User();
$this->getEntityManager()->persist($user);
$this->getEntityManager()->flush();
// Changes are rolled back after the test.
}
Custom Schema:
Override schema generation by implementing getSchema():
protected function getSchema(): ?Schema
{
$schema = new Schema();
$schema->createTable('custom_table');
return $schema;
}
Entity Loading:
getEntityDirectories() points to the correct paths (e.g., src/Entity).Schema Mismatches:
$this->getEntityManager()->getConnection()->getSchemaManager()->dropDatabase();
Event Listener Scope:
setUp() persist across test methods.tearDown():
public function tearDown(): void
{
$this->getEventManager()->clearListeners();
}
Performance:
inMemory SQLite for faster execution (default behavior).Inspect Schema: Dump the generated schema to verify:
public function testSchema()
{
$schema = $this->getEntityManager()->getConnection()->getSchemaManager()->createSchema();
echo $schema->toSql();
}
Enable SQL Logging: Temporarily enable logging to debug queries:
public function setUp(): void
{
$this->getEntityManager()->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());
}
Custom EntityManager:
Override createEntityManager() to configure the EntityManager:
protected function createEntityManager(Connection $connection): EntityManager
{
$em = EntityManager::create($connection, $this->getConfiguration());
$em->getConfiguration()->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache());
return $em;
}
Custom Connection: Replace the default SQLite connection:
protected function createConnection(): Connection
{
$params = [
'driver' => 'pdo_mysql',
'host' => 'localhost',
'dbname' => 'test_db',
'user' => 'root',
'password' => '',
];
return DriverManager::getConnection($params);
}
Test Isolation: Force a fresh database per test class (not per method):
protected function getEntityManager(): EntityManager
{
static $em = null;
if ($em === null) {
$em = parent::getEntityManager();
}
return $em;
}
Namespace Mapping:
Ensure your Entity directories are correctly mapped in Doctrine’s configuration (e.g., orm/naming_strategy or orm/mappings).
Proxy Generation: Disable proxies for faster tests (if not using inheritance mapping):
protected function getConfiguration(): Configuration
{
$config = new Configuration();
$config->setProxyDir(__DIR__ . '/../var/proxies');
$config->setProxyNamespace('Proxies');
$config->setAutoGenerateProxyClasses(false); // Disable for tests
return $config;
}
How can I help you explore Laravel packages today?