PHPUnit – How to mock multiple calls to the same method
When you write unit tests with PHPUnit there is a big chance that you have to mock several objects. If you want to write a unit test for your controller action and you use Doctrine ORM in your project, you will have to mock the Doctrine EntityManager, specific repositories and specify return values of the repository method calls. In the example below a repository method calls are “getCustomers” and “getNewHouse”.
The use of $this->at() (not possible)
Mocking multiple calls to the same method can be a bit difficult with PHPUnit. Because if you have multiple repository calls and you want to use $this->any(), the following will NOT work. A call to getRepository(‘Application\Entity\House’) will still expect a Application\Entity\Customer.
$entityManagerMock->expects($this->any())
->method('getRepository')
->with('Application\Entity\Customer')
->will($this->returnValue($customerRepositoryMock));
$entityManagerMock->expects($this->any())
->method('getRepository')
->with('Application\Entity\House')
->will($this->returnValue($houseRepositoryMock));
The use of $this->at(0) (not recommended)
A not recommended, but possible solutions is to use $this->at(0). With this you specify the call order. But that will make mocking still quite tricky and far from ideal.
$entityManagerMock->expects($this->any(0))
->method('getRepository')
->with('Application\Entity\Customer')
->will($this->returnValue($customerRepositoryMock));
$entityManagerMock->expects($this->any(1))
->method('getRepository')
->with('Application\Entity\House')
->will($this->returnValue($houseRepositoryMock));
The use of a closure (recommended!)
Code of the Application\Controller\IndexController:
namespace Application\Controller
;
use Doctrine\ORM\EntityManager
;
class IndexController
{
protected $entityManager;
public function __construct
(EntityManager
$entityManager)
{
$this->entityManager = $entityManager;
}
protected function getEntityManager
()
{
return $this->entityManager;
}
public function indexAction
()
{
$lines = [];
$newHouses = [];
$offset = 0;
$maxResults = 10;
do {
/** @var $customers Customer[] */
$customers = $this->getEntityManager()
->getRepository('Application\Entity\Customer')
->getCustomers($offset, $maxResults);
foreach ($customers as $customer) {
if ($customer->getAddress() == '') {
$lines[] = 'Customer with ID ' . $customer->getId()
. ' is homeless :-(' . "\n";
}
}
$newHouse = $this->getEntityManager()
->getRepository('Application\Entity\House')
->getNewHouse($customer);
$offset += $maxResults;
} while(count($customers) > 0);
return array(
'lines' => $lines,
'newHouses' => $newHouses,
);
}
}
To write a unit test for this controller action we want to go trough the “do” part twice. The first time $customers will contain three customers in our test and the second time it will contain zero customers, so it will leave the do while construct.
Code of the unit test:
<?php
namespace ApplicationTest\Controller
;
use Application\Entity\Customer
;
use Application\Entity\House
;
class IndexControllerTest
extends \PHPUnit_Framework_TestCase
{
public function testIndexAction
()
{
// Mock Doctrine
$entityManagerMock = $this->getDoctrineEntityManagerMock();
$entityManagerMock->expects($this->any())
->method('getRepository')
->with($this->anything())
->will($this->returnCallback(
function($entityName) {
static
$callCount = array();
$callCount[$entityName] = !isset($callCount[$entityName]) ?
1 : ++$callCount[$entityName];
if ($entityName === 'Application\Entity\House') {
$repositoryMethods = [
'getNewHouse' => $this->getNewHouse()
];
$repositoryMock = $this->getDoctrineRepositoryMock(
'Application\Entity\House',
$repositoryMethods
);
return $repositoryMock;
}
if ($entityName === 'Application\Entity\Customer') {
$repositoryMethods = array();
if ($callCount[$entityName] > 1) {
$repositoryMethods['getCustomers'] = array();
} else {
$repositoryMethods['getCustomers'] = $this->getCustomers();
}
$repositoryMock = $this->getDoctrineRepositoryMock(
'Application\Entity\Customer',
$repositoryMethods
);
return $repositoryMock;
}
}
));
$controller = new \Application\Controller\IndexController
($entityManagerMock);
$result = $controller->testIndexAction();
$this->assertArrayHasKey('lines', $result);
$this->assertArrayHasKey('newHouses', $result);
$this->assertGreatherThan(0, count($result['newHouses']));
$this->assertInstanceOf('Application\Entity\House', $result['newHouses'][0]);
}
/**
* Gets customers
*
* @return Customer[]
*/
protected function getCustomers
()
{
$customers = [];
$customer = new Customer
();
$customer->setName('Customer1');
$customers[] = $customer;
$customer = new Customer
();
$customer->setName('Customer2');
$customers[] = $customer;
$customer = new Customer
();
$customer->setName('Customer3');
$customers[] = $customer;
return $customers;
}
/**
* Gets a new house
*
* @return House
*/
protected function getNewHouse
()
{
$newHouse = new House
();
$newHouse->setAddress('Main Street 1');
return $newHouse;
}
/**
* Gets a Doctrine Entity Manager mock object
*
* @param boolean $mockDoctrineService
*
* @return type
*/
protected function getDoctrineEntityManagerMock
()
{
$entityManagerMock = $this->getMock('Doctrine\ORM\EntityManager',
array('getRepository', 'getClassMetadata', 'persist', 'flush'), array(), '', false);
$entityManagerMock->expects($this->any())
->method('persist')
->will($this->returnValue(null));
$entityManagerMock->expects($this->any())
->method('flush')
->will($this->returnValue(null));
$entityManagerMock->expects($this->any())
->method('getClassMetadata')
->will($this->returnValue((object
) array('name' => 'aClass')));
return $entityManagerMock;
}
/**
* Gets a Doctrine Repository mock object
*
* @param string $repository Repository name
* @param string|array $repositoryMethods Repository methods as
* [
* 'methodName' => [
* 'returnValue' => $returnValue,
* 'expects' => $expects
* ],
* ]
* @param mixed $returnValue
* @param mixed $expects
*
* @return object
*/
protected function getDoctrineRepositoryMock
($repository, $repositoryMethods, $returnValue = null, $expects = null)
{
if (is_string($repositoryMethods)) {
$repositoryMethods = [$repositoryMethods => ['returnValue' => $returnValue, 'expects' => $expects]];
}
$repositoryMock = $this->getMock($repository, array_keys($repositoryMethods), array(), '', false);
foreach ($repositoryMethods as $method => $options) {
if (is_array($options) && array_key_exists('returnValue', $options)) {
$expects = isset($options['expects']) ?
$options['expects'] : $this->once();
$returnValue = $options['returnValue'];
} else {
$expects = $this->once();
$returnValue = $options;
}
$repositoryMock->expects($expects)
->method($method)
->will($this->returnValue($returnValue));
}
return $repositoryMock;
}
}
As you can see in this unit test, the use of a closure will give you full flexibility in mocking multiple calls to the same method!
Another possible option is to use Mockery. Read more about mocking multiple calls with mockery.