How to mock EntityManager for phpUnit?

2019-08-02 04:15发布

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条回答
Explosion°爆炸
2楼-- · 2019-08-02 04:30

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

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

查看更多
登录 后发表回答