PHPUnit : Generating Test Doubles for Doctrine EntityManager class and mocking internal objects dependencies.

Entity Manager and DoctrineHydrator.

As part of a PHP project I’m currently working on, I had to create unit tests for a function that takes parameters from the result of a SQL query executed by Doctrine (Query Builder) and asserting it returns the same result in the form of an associative array containing the column names as keys and their associated values. The result can be:

  • An object
  • An array containing an object and scalar values
  • An array containing several objects

And to extract these values, I use the DoctrineHydrator class.

Function to test:

public function extract($results, $fetchMode = false)
{
	$hydrator  = new DoctrineHydrator($this->entityManager, $fetchMode);

	$className = $this->modelClass;
	$proxy     = false;
	
	if (!is_array($results)) {
		$proxy     = ($this->isProxy($results));
		$className = ClassUtils::getRealClass(get_class($results));
		$results   = $hydrator->extract($results);
	}
	
	return $this->getColumnOriginalName($results, $className, $proxy, $fetchMode);
}

These elements are injected by factories.

Problematic

  1. How can we mock DoctrineHydrator object ($hydrator) knowing it’s being instantiated inside extract function using `new` operator. (PHPUnit can not do anything in this case :D)
  2. How to initiate a real instance of the EntityManager object without touching our database? THIS is important for testing other features.

Solutions

  1. Since it is impossible to create a mock of an object that was created using the new operator inside a function, I created a function getDoctrineHydrator() in the same class that returns an object of type DoctrineHydrator
public function getDoctrineHydrator($entityManager, $fetchMode)
{
	return new DoctrineHydrator($entityManager, $fetchMode);
}

Then I replaced this line:

$hydrator = new DoctrineHydrator($this->entityManager, $fetchMode);

By:

$hydrator = $this->getDoctrineHydrator($this->entityManager, $fetchMode);

And in the test I created a test double of the DoctrineHydrator object, and changed the behavior of the getDoctrineHydrator() function to return this object:

$hydrator = $this->createMock(DoctrineHydrator::class);
$hydrator->method('extract')
->willReturn([]);

Aspect::double(CRUDAdaptor::class, ['getDoctrineHydrator' => $hydrator]);

2. In the setup function of the PHPUnit test class, I initialized a real instance of the EntityManager using PDO_SQLITE (in-memory database):

protected function setUp()
{
	$paths = [
		'../../../src/User/Model/User.php',
	];
	
	$config = Setup::createAnnotationMetadataConfiguration($paths, true, null, null, false);
	
	$dbParams = [
		'driver' => 'pdo_sqlite',
		'memory' => true
	];
	
	$this->entityManager = EntityManager::create($dbParams, $config);
}

This technique allows us to easily retrieve any model metadata, providing us the ability of fetching tables column names, model field name and all other meta information used in this test.

Youssef Senior PHP Developer
Aug 2018