How to mock EntityManager for phpUnit?

房东的猫 提交于 2019-12-24 09:27:54

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!