Php 如何在没有任何实体的情况下测试侦听器/订阅者

Php 如何在没有任何实体的情况下测试侦听器/订阅者,php,symfony,doctrine-orm,event-listener,subscriber,Php,Symfony,Doctrine Orm,Event Listener,Subscriber,我创建了一个AuditLoggerBundle*,它有一个使用条令事件(prePersist、preUpdate和preRemove)的服务,以便在审计日志表(AuditLog实体)中创建一个新条目 该捆绑包可以与我的其他捆绑包一起使用,但我想对其进行单元测试和功能测试 问题是,为了对AuditLoggerListener函数进行功能测试,我需要至少有两个“假”实体,可以保存、更新等 在这个捆绑包中,我不知道如何做到这一点,因为我只有一个AuditLog实体,我需要使用两个over实体(,仅在测

我创建了一个AuditLoggerBundle*,它有一个使用条令事件(prePersist、preUpdate和preRemove)的服务,以便在审计日志表(AuditLog实体)中创建一个新条目

该捆绑包可以与我的其他捆绑包一起使用,但我想对其进行单元测试和功能测试

问题是,为了对
AuditLoggerListener
函数进行功能测试,我需要至少有两个“假”实体,可以保存、更新等


在这个捆绑包中,我不知道如何做到这一点,因为我只有一个AuditLog实体,我需要使用两个over实体(,仅在测试中使用)

  • 第一个实体将是“可审核的”(我必须在 审核日志(如果我对此实体执行持久化、更新或删除操作)
  • 第二个将是“不可审核”(我不能有新条目) 在audit_log表中执行持久化、更新或删除时 该实体)*
  • 这两个实体可以与唯一的EntityClass相关,但不能是AuditLog的实例
  • 下面是我看到的持久化功能测试:

    <?php
    $animal = new Animal(); //this is a fake Auditable entity
    $animal->setName('toto');
    $em = new EntityManager(); //actually I will use the container to get this manager
    $em->persist($animal);
    $em->flush();
    
    //Here we test that I have a new line in audit_log table with the right informations
    
    听众:
    namespace Kali\AuditLoggerBundle\EventListener;
    
    use DateTime;
    use Doctrine\ORM\Event\LifecycleEventArgs;
    use Doctrine\ORM\EntityManager;
    use Doctrine\ORM\Event\PreUpdateEventArgs;
    use JMS\Serializer\SerializerInterface;
    use Kali\AuditLoggerBundle\Entity\AuditLog;
    use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
    use Symfony\Component\Serializer\Encoder\JsonEncoder;
    
    /**
     * Class AuditLoggerListener
     * insert a new entry in audit_log table for every doctrine event
     *
     * @package Kali\AuditLoggerBundle\EventListener
     */
    class AuditLoggerListener
    {
        /**
         * @var TokenStorage
         */
        protected $securityToken;
    
        /**
         * @var EntityManager
         */
        protected $em;
    
        /**
         * @var array
         */
        protected $auditableEntities;
    
        /**
         * @var array
         */
        protected $nonAuditableEntities  = ['Kali\AuditLoggerBundle\Entity\AuditLog'];
    
        /**
         * @var AuditLog
         */
        protected $auditLogger;
    
        /**
         * @var SerializerInterface
         */
        protected $serializer;
    
        /**
         * @param AuditLog $auditLogger
         * @param SerializerInterface $serializer
         * @param TokenStorage $securityToken
         * @param array $auditableEntities
         * @param array $nonAuditableEntities
         */
        public function __construct(
            AuditLog $auditLogger,
            SerializerInterface $serializer,
            TokenStorage $securityToken,
            $auditableEntities = [],
            $nonAuditableEntities = []
        ) {
            $this->auditLogger          =   $auditLogger;
            $this->serializer           =   $serializer;
            $this->securityToken        =   $securityToken;
            $this->auditableEntities    =   $auditableEntities;
            //add all non auditable entities to the current array of non auditable entities
            array_merge($this->nonAuditableEntities, $nonAuditableEntities);
        }
    
        /**
         *
         * @param LifecycleEventArgs $args
         *
         * @return boolean
         */
        public function prePersist(LifecycleEventArgs $args)
        {
            $this->em   =   $args->getEntityManager();
            $entity     =   $args->getEntity();
    
            $this->em
                ->getEventManager()
                ->removeEventListener('prePersist', $this);
    
            if ($this->isAuditableEntity($entity)) {
                $this->addAudit(
                    $this->securityToken->getToken()->getUsername(),
                    "INSERT",
                    get_class($entity),
                    $this->serializer->serialize($entity, JsonEncoder::FORMAT)
                );
            }
    
            return true;
        }
    
        /**
         *
         * @param PreUpdateEventArgs $args
         *
         * @return boolean
         */
        public function preUpdate(PreUpdateEventArgs $args)
        {
            $this->em   =   $args->getEntityManager();
            $entity     =   $args->getEntity();
    
            $this->em
                ->getEventManager()
                ->removeEventListener('preUpdate', $this);
    
            if ($this->isAuditableEntity($entity)) {
                $this->addAudit(
                    $this->securityToken->getToken()->getUsername(),
                    "UPDATE",
                    get_class($entity),
                    $this->serializer->serialize($entity, JsonEncoder::FORMAT),
                    $this->serializer->serialize($args->getEntityChangeSet(), JsonEncoder::FORMAT)
                );
            }
    
            return true;
        }
    
        /**
         *
         * @param LifecycleEventArgs $args
         *
         * @return boolean
         */
        public function preRemove(LifecycleEventArgs $args)
        {
            $this->em   =   $args->getEntityManager();
            $entity     =   $args->getEntity();
    
            $this->em
                ->getEventManager()
                ->removeEventListener('preRemove', $this);
    
            if ($this->isAuditableEntity($entity)) {
                $this->addAudit(
                    $this->securityToken->getToken()->getUsername(),
                    "REMOVE",
                    get_class($entity),
                    $this->serializer->serialize($entity, JsonEncoder::FORMAT)
                );
            }
    
            return true;
        }
    
        /**
         * Insert a new line in audit_log table
         *
         * @param string      $user
         * @param string      $action
         * @param string      $entityClass
         * @param null|string $entityValue
         * @param null|string $entityChange
         *
         * @return void
         */
        private function addAudit($user, $action, $entityClass, $entityValue = null, $entityChange = null)
        {
            if ($this->auditLogger) {
                $this->auditLogger
                    ->setUser($user)
                    ->setAction($action)
                    ->setEntityClass($entityClass)
                    ->setEntityValue($entityValue)
                    ->setEntityChange($entityChange)
                    ->setDate(new DateTime());
            }
    
            if ($this->em) {
                $this->em->persist($this->auditLogger);
                $this->em->flush();
            }
        }
    
        /**
         * check if an entity is auditable
         *
         * @param $entity
         *
         * @return bool
         */
        private function isAuditableEntity($entity)
        {
            $auditable = false;
    
            //the entity must not be in the non auditable entity array
            if (!in_array(get_class($entity), $this->nonAuditableEntities)
                && (empty($this->auditableEntities) || (!empty($this->auditableEntities) && in_array(get_class($entity), $this->auditableEntities)))
            ) {
                $auditable = true;
            }
    
            return $auditable;
        }
    }
    
    我想测试这个侦听器的preXXXX函数。。。
    因此,例如,我需要测试当我在一个伪实体上持久化时(我真的不知道如何模拟),我的审计日志表中是否有一个新条目…

    单元测试一个php类意味着只测试这个类包含的代码,而不需要任何外部交互。 因此,您应该模拟所有外部服务:请参阅phpunit模拟文档

    例如,如果您的类看起来像这样:

    <?php
    class AuditLogListener 
    {
        ...
        function postPersist($event)
        {
            $animal = new Animal();
            $em = $event->getEm();
            $em->persist($animal);
        }
        ...
    }
    
    
    在共享包上进行功能测试几乎是不可能的,因为您不能依赖Symfony2发行版。我认为在这种情况下,最好的办法是对包进行适当的单元测试。
    -奥劳伦多

    以下是与侦听器相关的测试类(该类的覆盖率为100%):


    谢谢你的回答,我不确定这个方法。。。所以如果我明白了,我需要模拟监听器和LifeCycleEvent对象来测试函数的响应。。。再次感谢你澄清这一点。这很容易定义。或多或少,除了要测试的类之外,您必须模拟所有内容。如果您想测试侦听器并验证他是否使用生命周期事件中的对象并对其进行处理,那么不要模拟您的侦听器,而是模拟其余的侦听器。在我的示例中,如果不调用“persist”方法,测试将失败。我忘记了一些东西。。。实体管理器实际上需要保留一个新的AuditLog实体,该实体包含以下信息:-用户名,-日期,-操作('insert','update','remove')-entityClass(这里必须是Animal…这是一个问题)-entityValue(实体的json表示形式)-entityChange(如果这是一个更新,它等于我模拟的LifeCycleEventArgs的getEntityChangeSet函数)也许我应该发布我的侦听器代码和service.yml以便您理解?构建$event变量的链接不太正确。链接需要在getMock()处结束因此,$event对象是一个模拟对象。然后,$event->expects()…在我意识到需要中断它之前,这让我有点抓狂,以便调用postPersist($event)可以。的确@DavidBaucum谢谢你的评论,我已经更新了答案如果你想在你的数据库中看到一个新条目,这不是一个单元测试,它将是一个函数测试,你可以使用像SF2 WebTestCase或behat这样的框架。单元测试只会检查你类的IO,不会有数据库交互,你会d只需检查EntityManager方法是否被调用,并且它应该是足够的,因为EntityManager也是经过单元测试的一个可信任的方法。好的,我理解这一点,我可以对我的函数进行一些单元测试,以确保某些函数确实被调用(例如persist、flush、getEntity、getEntityManager等).所以现在我想为这个监听器做一个功能测试。我已经在其他项目上做了很多功能测试,但它们都使用HMI或我可以使用的真实实体(没有模拟)。在这个捆绑包中,我不知道如何做,因为我只有一个AuditLog实体,我需要使用两个以上的实体(只在测试中使用)。第一个实体将是“可审核的”(如果我对此实体执行持久化、更新或删除操作,则必须在审核日志中有一个新条目)。第二个实体将是“不可审核的”(当我对此实体执行持久化、更新或删除操作时,审核日志表中不得有新条目).对于Functional测试,您仍然可以使用Mock,但在您的情况下,我认为最简单的方法应该是创建一个可以在测试中使用的虚拟实体。您所说的“虚拟实体”是什么意思?
    
    <?php
    class AuditLogListener 
    {
        ...
        function postPersist($event)
        {
            $animal = new Animal();
            $em = $event->getEm();
            $em->persist($animal);
        }
        ...
    }
    
    <?php
    class AuditLogListenerTest
    {
        private $em;
        ...
        function testPostPersist()
        {
            $em = $this->getMockBuilder('stdClass')
                     ->setMethods(array('persist'))
                     ->getMock();
    
            $em->expects($this->once())
                     ->method('persist')
                     ->with($this->isInstanceOf('Animal'));
    
            $event = $this->getMockBuilder('stdClass')
                     ->setMethods(array('getEm'))
                     ->getMock();
    
            $event->expects($this->once())
                     ->method('getEm')
                     ->will($this->returnValue($em));
    
            $listener = new AuditLogListener();
            $listener->postPersist($event);
        }
        ...
    }
    
    <?php
    
    namespace Kali\AuditLoggerBundle\Tests\Controller;
    
    use Kali\AuditLoggerBundle\Entity\AuditLog;
    use Kali\AuditLoggerBundle\EventListener\AuditLoggerListener;
    use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
    
    /**
     * Class AuditLoggerListenerTest
     * @package Kali\AuditLoggerBundle\Tests\Controller
     */
    class AuditLoggerListenerTest extends WebTestCase
    {
        protected static $container;
    
        /**
         * This method is called before the first test of this test class is run.
         *
         * @since Method available since Release 3.4.0
         */
        public static function setUpBeforeClass()
        {
            self::$container = static::createClient()->getContainer();
        }
    
    /*
     * ===========================================================================
     * TESTS ON AUDITABLE ENTITIES
     * ===========================================================================
     */
        /**
         * test prepersist function
         */
        public function testPrePersistWithAuditableEntity()
        {
            //Mock all the needed objects
            $token          =   $this->mockToken();
            $tokenStorage   =   $this->mockTokenStorage();
            $eventManager   =   $this->mockEventManager();
            $entityManager  =   $this->mockEntityManager();
            $entity         =   $this->mockEntity();
            $lifeCycleEvent =   $this->mockEvent('LifecycleEventArgs');
    
            //assert the methods that must be called or not
            $token          ->  expects($this->once())->method('getUsername');
            $tokenStorage   ->  expects($this->once())->method('getToken')->willReturn($token);
            $eventManager   ->  expects($this->once())->method('removeEventListener');
            $entityManager  ->  expects($this->once())->method('getEventManager')->willReturn($eventManager);
            $entityManager  ->  expects($this->once())->method('persist');
            $lifeCycleEvent ->  expects($this->never())->method('getEntityChangeSet');
            $lifeCycleEvent ->  expects($this->once())->method('getEntityManager')->willReturn($entityManager);
            $lifeCycleEvent ->  expects($this->once())->method('getEntity')->willReturn($entity);
    
            //instanciate the listener
            $listener = new AuditLoggerListener(
                new AuditLog(),
                self::$container->get('jms_serializer'),//Yes this is not really good to do that
                $tokenStorage
            );
            // call the function to test
            $listener->prePersist($lifeCycleEvent);
        }
    
        /**
         * test preUpdate function
         */
        public function testPreUpdateWithAuditableEntity()
        {
            //Mock all the needed objects
            $token          =   $this->mockToken();
            $tokenStorage   =   $this->mockTokenStorage();
            $eventManager   =   $this->mockEventManager();
            $entityManager  =   $this->mockEntityManager();
            $entity         =   $this->mockEntity();
            $lifeCycleEvent =   $this->mockEvent('PreUpdateEventArgs');
    
            //assert the methods that must be called or not
            $token          ->  expects($this->once())->method('getUsername');
            $tokenStorage   ->  expects($this->once())->method('getToken')->willReturn($token);
            $eventManager   ->  expects($this->once())->method('removeEventListener');
            $entityManager  ->  expects($this->once())->method('getEventManager')->willReturn($eventManager);
            $entityManager  ->  expects($this->once())->method('persist');
            $lifeCycleEvent ->  expects($this->once())->method('getEntityChangeSet');
            $lifeCycleEvent ->  expects($this->once())->method('getEntityManager')->willReturn($entityManager);
            $lifeCycleEvent ->  expects($this->once())->method('getEntity')->willReturn($entity);
    
            //instanciate the listener
            $listener = new AuditLoggerListener(
                new AuditLog(),
                self::$container->get('jms_serializer'),//Yes this is not really good to do that
                $tokenStorage
            );
            // call the function to test
            $listener->preUpdate($lifeCycleEvent);
        }
    
        /**
         * test PreRemove function
         */
        public function testPreRemoveWithAuditableEntity()
        {
            //Mock all the needed objects
            $token          =   $this->mockToken();
            $tokenStorage   =   $this->mockTokenStorage();
            $eventManager   =   $this->mockEventManager();
            $entityManager  =   $this->mockEntityManager();
            $entity         =   $this->mockEntity();
            $lifeCycleEvent =   $this->mockEvent('LifecycleEventArgs');
    
            //assert the methods that must be called or not
            $token          ->  expects($this->once())->method('getUsername');
            $tokenStorage   ->  expects($this->once())->method('getToken')->willReturn($token);
            $eventManager   ->  expects($this->once())->method('removeEventListener');
            $entityManager  ->  expects($this->once())->method('getEventManager')->willReturn($eventManager);
            $entityManager  ->  expects($this->once())->method('persist');
            $lifeCycleEvent ->  expects($this->never())->method('getEntityChangeSet');
            $lifeCycleEvent ->  expects($this->once())->method('getEntityManager')->willReturn($entityManager);
            $lifeCycleEvent ->  expects($this->once())->method('getEntity')->willReturn($entity);
    
            //instanciate the listener
            $listener = new AuditLoggerListener(
                new AuditLog(),
                self::$container->get('jms_serializer'),//Yes this is not really good to do that
                $tokenStorage
            );
            // call the function to test
            $listener->preRemove($lifeCycleEvent);
        }
    
    /*
     * ===========================================================================
     * TESTS ON NON AUDITABLE ENTITIES
     * ===========================================================================
     */
        /**
         * test prepersit function
         */
        public function testPrePersistWithNonAuditableEntity()
        {
            //Mock all the needed objects
            $token          =   $this->mockToken();
            $tokenStorage   =   $this->mockTokenStorage();
            $eventManager   =   $this->mockEventManager();
            $entityManager  =   $this->mockEntityManager();
            $entity         =   new AuditLog();//this entity is non Auditable
            $lifeCycleEvent =   $this->mockEvent('LifecycleEventArgs');
    
            //assert the methods that must be called or not
            $token          ->  expects($this->never())->method('getUsername');
            $tokenStorage   ->  expects($this->never())->method('getToken')->willReturn($token);
            $eventManager   ->  expects($this->once())->method("removeEventListener");
            $entityManager  ->  expects($this->never())->method('persist');
            $entityManager  ->  expects($this->once())->method('getEventManager')->willReturn($eventManager);
            $lifeCycleEvent ->  expects($this->never())->method('getEntityChangeSet');
            $lifeCycleEvent ->  expects($this->once())->method('getEntityManager')->willReturn($entityManager);
            $lifeCycleEvent ->  expects($this->once())->method('getEntity')->willReturn($entity);
    
            $listener = new AuditLoggerListener(
                new AuditLog(),
                self::$container->get('jms_serializer'),
                $tokenStorage
            );
    
            $listener->prePersist($lifeCycleEvent);
        }
    
        /**
         * test prepersit function
         */
        public function testPreUpdateWithNonAuditableEntity()
        {
            //Mock all the needed objects
            $token          =   $this->mockToken();
            $tokenStorage   =   $this->mockTokenStorage();
            $eventManager   =   $this->mockEventManager();
            $entityManager  =   $this->mockEntityManager();
            $entity         =   new AuditLog();//this entity is non Auditable
            $lifeCycleEvent =   $this->mockEvent('PreUpdateEventArgs');
    
            //assert the methods that must be called or not
            $token          ->  expects($this->never())->method('getUsername');
            $tokenStorage   ->  expects($this->never())->method('getToken')->willReturn($token);
            $eventManager   ->  expects($this->once())->method("removeEventListener");
            $entityManager  ->  expects($this->never())->method('persist');
            $entityManager  ->  expects($this->once())->method('getEventManager')->willReturn($eventManager);
            $lifeCycleEvent ->  expects($this->never())->method('getEntityChangeSet');
            $lifeCycleEvent ->  expects($this->once())->method('getEntityManager')->willReturn($entityManager);
            $lifeCycleEvent ->  expects($this->once())->method('getEntity')->willReturn($entity);
    
            $listener = new AuditLoggerListener(
                new AuditLog(),
                self::$container->get('jms_serializer'),
                $tokenStorage
            );
    
            $listener->preUpdate($lifeCycleEvent);
        }
    
        /**
         * test preRemove function
         */
        public function testPreRemoveWithNonAuditableEntity()
        {
            //Mock all the needed objects
            $token          =   $this->mockToken();
            $tokenStorage   =   $this->mockTokenStorage();
            $eventManager   =   $this->mockEventManager();
            $entityManager  =   $this->mockEntityManager();
            $entity         =   new AuditLog();//this entity is non Auditable
            $lifeCycleEvent =   $this->mockEvent('LifecycleEventArgs');
    
            //assert the methods that must be called or not
            $token          ->  expects($this->never())->method('getUsername');
            $tokenStorage   ->  expects($this->never())->method('getToken')->willReturn($token);
            $eventManager   ->  expects($this->once())->method("removeEventListener");
            $entityManager  ->  expects($this->never())->method('persist');
            $entityManager  ->  expects($this->once())->method('getEventManager')->willReturn($eventManager);
            $lifeCycleEvent ->  expects($this->never())->method('getEntityChangeSet');
            $lifeCycleEvent ->  expects($this->once())->method('getEntityManager')->willReturn($entityManager);
            $lifeCycleEvent ->  expects($this->once())->method('getEntity')->willReturn($entity);
    
            $listener = new AuditLoggerListener(
                new AuditLog(),
                self::$container->get('jms_serializer'),
                $tokenStorage
            );
    
            $listener->preRemove($lifeCycleEvent);
        }
    
    /*
     * ===========================================================================
     * MOCKS
     * ===========================================================================
     */
    
        /**
         * Mock a Token object
         *
         * @return \PHPUnit_Framework_MockObject_MockObject
         */
        private function mockToken()
        {
            $token = $this->getMock(
                'Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken',
                ['getUsername'],
                [],
                '',
                false
            );
    
            return $token;
        }
    
        /**
         * Mock a TokenStorage object
         *
         * @return \PHPUnit_Framework_MockObject_MockObject
         */
        private function mockTokenStorage()
        {
            //mock tokenStorage
            $tokenStorage = $this->getMock(
                'Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage',
                ['getToken'],
                [],
                '',
                false
            );
    
            return $tokenStorage;
        }
    
        /**
         * Mock an EventManager Object
         *
         * @return \PHPUnit_Framework_MockObject_MockObject
         */
        private function mockEventManager()
        {
            //mock the event manager
            $eventManager = $this->getMock(
                '\Doctrine\Common\EventManager',
                ['removeEventListener'],
                [],
                '',
                false
            );
    
            return $eventManager;
        }
    
        /**
         * Mock an EntityManager
         *
         * @return \PHPUnit_Framework_MockObject_MockObject
         */
        private function mockEntityManager()
        {
            //mock the entityManager
            $emMock = $this->getMock(
                '\Doctrine\ORM\EntityManager',
                ['getEventManager', 'persist', 'update', 'remove', 'flush'],
                [],
                '',
                false
            );
    
            return $emMock;
        }
    
        /**
         * Mock an Entity Object
         *
         * @return \PHPUnit_Framework_MockObject_MockObject
         */
        private function mockEntity()
        {
            $entity = $this->getMockBuilder('stdClass')
                           ->setMethods(['getName', 'getType'])
                           ->getMock();
    
            $entity->expects($this->any())
                   ->method('getName')
                   ->will($this->returnValue('toto'));
            $entity->expects($this->any())
                   ->method('getType')
                   ->will($this->returnValue('chien'));
    
            return $entity;
        }
    
        /**
         * mock a lifeCycleEventArgs Object
         *
         * @param $eventType
         *
         * @return \PHPUnit_Framework_MockObject_MockObject
         */
        private function mockEvent($eventType)
        {
            $lifeCycleEvent = $this->getMock(
                '\Doctrine\ORM\Event\\'.$eventType,
                ['getEntityManager', 'getEntity', 'getEntityChangeSet'],
                [],
                '',
                false
            );
    
            return $lifeCycleEvent;
        }
    }