问题
I am writing a Unit test for this class:
<?php
namespace AppBundle\Managers\CRUD;
use Doctrine\ORM\EntityManager;
use CommonLibs\Interfaces\CrudManagerInterface;
use AppBundle\Entity\Pet;
use CommonLibs\Helpers\PaginatorHelper;
use AppBundle\Entity\Person;
use AppBundle\Managers\CRUD\PetManager;
use AppBundle\AppBundle;
class PersonManager extends CrudManagerInterface
{
/**
* @var EntityManager
*/
private $em;
/**
* @var PetManager
*/
private $petManager;
public function __construct(EntityManager $em,PetManager $petManager)
{
$this->em=$em;
$this->petManager=$petManager;
}
/**
* {@inheritDoc}
* @see \CommonLibs\Interfaces\CrudManagerInterface::search()
* @return AppBundle\Entity\Person[]
*/
public function search(array $searchParams, array $order, $page, $limit)
{
$queryBuilder=$this->em->createQueryBuilder();
$queryBuilder=$queryBuilder->select('p')->from('AppBundle:Person','p');
if(isset($searchParams[Person::NAME])) {
$queryBuilder->andWhere('p.name LIKE :name')->setParameter('name','%'.$searchParams[Person::NAME].'%');
}
$petNameSearch=isset($searchParams[Pet::NAME]);
$petTypeSearch=isset($searchParams[Pet::TYPE]);
if( $petNameSearch || $petTypeSearch ) {
$queryBuilder->join('p.pets','pe');
if($petNameSearch) {
$queryBuilder->andWhere('pe.name LIKE :pet_name')->setParameter('pet_name','%'.$searchParams[Pet::NAME].'$');
}
if($petTypeSearch) {
if(!is_array($searchParams[Pet::TYPE])) {
$searchParams[Pet::TYPE]=array($searchParams[Pet::TYPE]);
}
$queryBuilder->andWhere('pe.type IN (:pet_types)')->setParameter('pet_types',$searchParams[Pet::TYPE]);
}
/**
* @var Doctrine\ORM\Query
*/
$query=$queryBuilder->getQuery();
if((int)$limit>0) {
$query->setFirstResult(PaginatorHelper::calculateFirstResult($page,$limit))->setMaxResults((int)$limit);
}
$results=$query->getResult();
return $results;
}
}
/**
* {@inheritDoc}
* @see \CommonLibs\Interfaces\CrudManagerInterface::getById()
* @return AppBundle\Entity\Person
*/
public function getById($id)
{
return $this->em->getManager('AppBundle:Person')->findById($id);
}
/**
* {@inheritDoc}
* @see \CommonLibs\Interfaces\CrudManagerInterface::add()
*
* @param array $dataToAdd
*
* $dataToAdd Must have one of the follofiwng formats:
*
* FORMAT 1:
* [
* Person:NAME=>"value"
* ]
*
* FORMAT 2:
*
* [
* [
* Person:NAME=>"value"
* ],
* [
* Person:NAME=>"value"
* ],
* [
* Person:NAME=>"value"
* ]
* ]
*
* @return AppBundle\Entiry\Person[] with the modified persons
*/
public function add(array $dataToAdd)
{
/**
* @var AppBundle\Entiry\Person $insertedPersons
*/
$insertedPersons=[];
foreach($dataToAdd as $key=>$data) {
$personToInsert=new Person();
if(is_array($data)) {
$personToInsert=$this->add($data);
if($personToInsert==false) {
return false;
}
} elseif(!$this->setReference($personToInsert,$key,$data)) {
$personToInsert->$$key=$data;
}
if(is_array($personToInsert)) {
$insertedPersons=array_merge($insertedPersons,$personToInsert);
} else {
$this->em->flush($personToInsert);
$insertedPersons[]=$personToInsert;
}
}
if(!empty($insertedPersons)) {
$this->em->flush();
}
return $insertedPersons;
}
/**
* {@inheritDoc}
* @see \CommonLibs\Interfaces\CrudManagerInterface::edit()
*/
public function edit(array $changedData)
{
$em=$this->em->getManager('AppBundle:Person');
foreach($changedData as $id => $fieldsToChange) {
$item=$this->getById($id);
foreach($fieldsToChange as $fieldName=>$fieldValue){
if(!$this->setReference($item,$fieldName,$fieldValue)){
$item->$$fieldName=$fieldValue;
}
}
$em->merge($item);
}
$em->flush();
}
/**
* {@inheritDoc}
* @see \CommonLibs\Interfaces\CrudManagerInterface::delete()
*
* @param array changedData
* Should contain data in the following formats:
* FORMAT 1:
*
* [
* Person::ID=>^an_id^
* Person::NAME=>^a name of a person^
* ]
*
* FORMAT2:
* [
* Person::ID=>[^an_id1^,^an_id2^,^an_id3^...,^an_idn^]
* Person::NAME=>^a name of a person^
* ]
*
* The $changedData MUST contain at least one of Person::ID or Person::NAME.
* Therefore you can ommit one of Person::ID or Person::NAME but NOT both.
*/
public function delete(array $changedData)
{
$queryBuilder=$this->em->createQueryBuilder();
$queryBuilder->delete()->from('AppBundle:Person','p');
$canDelete=false;
if(isset($changedData[Person::ID])) {
if(!is_array($changedData[Person::ID])) {
$changedData[Person::ID]=[$changedData[Person::ID]];
}
$queryBuilder->where('person.id IN (:id)')->bindParam('id',$changedData[Person::ID]);
$canDelete=true;
}
if(isset($changedData[Person::NAME])) {
$queryBuilder->orWhere('person.name is :name')->bindParam('name',$changedData[Person::NAME]);
$canDelete=true;
}
if($canDelete) {
$query=$queryBuilder->getQuery();
$query->execute();
}
return $canDelete;
}
/**
* Set referencing fields to person.
*
* @param AppBundle\Entiry\Person $item The item to set the reference
* @param string $referencingKey A string that Indicates the input field.
* The strings for the param above are defined as constants at AppBundle\Entiry\Person.
* @param mixed $referencingValue The value of referencing key
*
* @return boolean
*/
private function setReference($item,$referencingKey,$referencingValue)
{
/**
* @var AppBundle\Entity\Pet $pet
*/
$pet=null;
if($referencingKey===Person::PET) {
if(is_numeric($referencingValue)) {//Given pet id
$pet=$this->petManager->getById($referencingValue);//Searching pet by id
} elseif (is_object($referencingValue)
&& $referencingValue instanceof AppBundle\Entity\Pet
){//Given directly a pet Object
$pet=$referencingValue;
}
$item->$$referencingKey=$referencingValue;
return true;
}
return false;
}
}
And I want to Mock up the Doctrine's entity Manager. But I do not know what to return in order to successfully use the Doctrine's Query Builder but without an actual Database Connection.
回答1:
Well, if you want really follow the best practices, you should not mock entity manager as you don't own it; you can read more at following links
https://github.com/mockito/mockito/wiki/How-to-write-good-tests
https://adamwathan.me/2017/01/02/dont-mock-what-you-dont-own/
https://8thlight.com/blog/eric-smith/2011/10/27/thats-not-yours.html
Ok, now, if you want to walk that road, you can mock EntityManager
like you mock every other objects in PHPUnit
if you work with PHPUnit >= 5.7 and PHP > 5.5
$mockedEm = $this->createMock(EntityManager::class)
or for PHP <= 5.5
$mockedEm = $this->createMock('Doctrine\\ORM\\EntityManager');
Once you mocked it, you have to declare all canned response and expectations: canned response in order to let your code work whereas expectations in order to let it to be a mock
Just to make an example, this should be canned
return $this->em->getManager('AppBundle:Person')->findById($id);
As you can will see, declare a canned method for every method call could be very difficult and overkill; for instance, here, you should act that way
$mockedEm = $this->createMock(EntityManager::class)
$mockedPersonManager = $this->createMock(...);
$mockedEm->method('getManager')->willReturn(mockedPersonManager);
$mockedPersonManager->findOneBy(...)->willReturn(...);
(of course you have to substitute ...
with real values)
Finally, remember that Mocks are not Stubs
来源:https://stackoverflow.com/questions/42176036/how-to-mock-entitymanager-for-phpunit