Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Doctrine Orm Test Service Provider Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install Dependencies:

    composer require matthiasnoback/phpunit-test-service-container
    composer require matthiasnoback/doctrine-orm-test-service-provider
    
  2. Configure PHPUnit: Add the service container to your phpunit.xml:

    <phpunit>
        <extensions>
            <extension class="Noback\PHPUnitTestServiceContainer\PHPUnit\ServiceContainerExtension"/>
        </extensions>
    </phpunit>
    
  3. 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([]));
        }
    }
    

Key First Use Case

  • Isolated Database per Test: Each test method gets a fresh SQLite database with schema auto-generated from your entities.
  • No Manual Setup: No need to configure Doctrine’s EntityManager or database connections manually.

Implementation Patterns

Core Workflow

  1. 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...
        }
    }
    
  2. 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);
    }
    
  3. Event Listeners/Subscribers: Register listeners dynamically:

    public function setUp(): void
    {
        $eventManager = $this->getEventManager();
        $eventManager->addEventListener(
            'prePersist',
            new AuditListener()
        );
    }
    
  4. 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);
    }
    

Integration Tips

  • 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;
    }
    

Gotchas and Tips

Pitfalls

  1. Entity Loading:

    • Issue: Entities not found? Ensure getEntityDirectories() points to the correct paths (e.g., src/Entity).
    • Fix: Use absolute paths and verify the directory exists.
  2. Schema Mismatches:

    • Issue: Tests fail with "Table not found" errors.
    • Fix: Clear cached metadata or rebuild the schema:
      $this->getEntityManager()->getConnection()->getSchemaManager()->dropDatabase();
      
  3. Event Listener Scope:

    • Issue: Listeners registered in setUp() persist across test methods.
    • Fix: Register listeners per test method or clear them in tearDown():
      public function tearDown(): void
      {
          $this->getEventManager()->clearListeners();
      }
      
  4. Performance:

    • Issue: Slow tests due to schema regeneration.
    • Fix: Use inMemory SQLite for faster execution (default behavior).

Debugging Tips

  • 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());
    }
    

Extension Points

  1. 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;
    }
    
  2. 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);
    }
    
  3. 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;
    }
    

Config Quirks

  • 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;
    }
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky
spatie/mailcoach-vapor