Jeroen De Dauw
Jeroen De Dauw

Reputation: 10918

Testing Doctrine EntityManager write failures

In my PHP application I am using Doctrine ORM in the data access later. In this layer I have services such as repositories that typically use Doctrines EntityManager. In those services the methods that do some form of modification typically follow this pattern:

public function modifyStuff( /* ... */ ) {
    try {
        $stuff = $this->entityManager->find( /* ... */ )
    }
    catch ( ORMException $ex ) {
        /* ... */
    }

    // Poke at $stuff

    try {
        $this->entityManager->persist( $stuff );
        $this->entityManager->flush();
    }
    catch ( ORMException $ex ) {
        /* code that needs to be tested */
    }
}

I'm trying to find a way to test the code in the second catch block: the code that handles write failures. So in my test I need to make the EntityManager throw when something is written. And naturally I want to minimize binding in my test to both the implementation of this repository (ie which doctrine methods are used) and the EntityManager interface and implementation itself. Ideally I could do something like

$entityManager = new TestEntityManager();
$entityManager->throwOnWrite();

after which the EntityManager would function normally except that it would throw on write. (I have test doubles like that for my repositories.)

I tried using the PHPUnit mock API as follows:

$entityManager = $this->getMockBuilder( EntityManager::class )->disableOrgninalConstructor()->getMock()
$entityManager->expects( $this->any() )
    ->method( 'persist' )
    ->willThrowException( new ORMException() );

This is not ideal since now my test binds to persist method, though this is not that big of an issue. This does not work though since for the service to function its constructor needs some arguments. Then I tried

$entityManager =
    $this->getMockBuilder( EntityManager::class )
        ->setConstructorArgs( [
            $this->entityManager->getConnection(),
            $this->entityManager->getConfiguration(),
            $this->entityManager->getEventManager()
        ] )
        ->getMock();

And found out that the EntityManager constructor is not public. So it seems like I won't be able to use the PHPUnit mocking API.

Any ideas on how to make the EntityManager throw on write, or otherwise test the code supposed to handle that case?

Upvotes: 3

Views: 982

Answers (1)

Tns
Tns

Reputation: 410

You can simulate write failure by throwing such exception from onFlush event handler. Consider this example:

$p = new Entity\Product();
$p->setName("Test Product");
$em->persist($p);
$em->flush();

$em->getEventManager()->addEventListener(\Doctrine\ORM\Events::onFlush, new WriteErrorListener());


// Nothing changed, no exceptions.
$p->setName("Test Product");
$em->flush();

try {
    $p->setName("Name changed");
    $em->flush();
    echo "\n\n    UNEXPECTED\n\n";
} catch (\Doctrine\ORM\ORMException $ex) {
    echo $ex->getMessage() . "\n";
}

WriteErrorListener:

use \Doctrine\ORM\Query\QueryException;

class WriteErrorListener 
{
    public function onFlush(\Doctrine\ORM\Event\OnFlushEventArgs $eventArgs)
    {
        $em = $eventArgs->getEntityManager();
        $uow = $em->getUnitOfWork();

        foreach ($uow->getScheduledEntityInsertions() as $entity) {
            // You can inspect $entity and provide additional information
            throw new QueryException('[Test condition] Insertion of entity pending');
        }

        foreach ($uow->getScheduledEntityUpdates() as $entity) {
            throw new QueryException('[Test condition] Update of entity pending');
        }
        // And so on...
    }
}

Run:

$ php app.php 
[Test condition] Update of entity pending

Upvotes: 4

Related Questions