我创建了一个AuditLoggerBundle*,它有一个使用Doctrine Events (prePersist, preUpdate和preRemove)的服务,以便在audit_log表(AuditLog Entity)中创建一个新条目。
这个包与我的其他包一起工作得很好,但是我想对它进行单元测试和功能测试。
问题是,为了对AuditLoggerListener
函数进行功能测试,我需要至少有两个"假"实体,我可以持久化,更新等。
在这个包中,我不知道如何做到这一点,因为我只有一个AuditLog实体,我需要使用两个over实体(将只在测试中使用)。
- 第一个实体将是"可审计的"(我必须有一个新的条目在 如果我在这个实体上做一个持久化,更新或删除,audit_log)。
- 第二个将是"不可审计的"(我不能有一个新的条目在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
所以我的问题是,我没有任何动物实体在我的包,我只需要这个来测试包,所以它必须只在测试数据库中创建,而不是在生产环境中(当我做app/console doctrine:schema:update --force
EDIT_1:阅读您的答案后,将执行AuditLoggerListener函数的单元测试,但我仍然想进行功能测试
*是的,我知道有很多,但他们不符合我所寻找的。
谢谢你的回答,我希望它能帮助一些人!
EDIT_2:这里是代码服务:
services:
#add a prefix to the auditLogger table
kali_audit_logger.doctrine.table.prefix:
class: Kali'AuditLoggerBundle'EventListener'TablePrefixListener
arguments: [%application.db.table.prefix%]
tags:
- { name: doctrine.event_listener, event: loadClassMetadata }
#audit all doctrine actions made by a user
kali_audit_logger.doctrine.event.logger:
class: Kali'AuditLoggerBundle'EventListener'AuditLoggerListener
arguments: [@kali_audit_log, @jms_serializer.serializer, @security.token_storage, %application.auditable.entities%, %application.non.auditable.entities%]
tags:
- { name: doctrine.event_listener, event: prePersist }
- { name: doctrine.event_listener, event: preUpdate }
- { name: doctrine.event_listener, event: preRemove }
# new AuditLog
kali_audit_log:
class: Kali'AuditLoggerBundle'Entity'AuditLog
侦听器:
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函数…因此,例如,我需要测试当我对假实体(我真的不知道如何模拟)进行持久化时,是否在我的audit_log表中有一个新条目…
在共享包上做功能测试几乎是不可能的,因为你不能依赖Symfony2发行版。我认为在这种情况下,最好的做法是对bundle进行适当的单元测试。——olaurendeau
下面是与侦听器相关的测试类(该类的100%覆盖率):
<?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;
}
}
如果你有什么要说的,请留下评论:)(例如,我可以将"mock all needed objects"部分重构为一个函数)
单元测试一个php类意味着只测试这个类包含的代码,不需要任何外部交互。因此,您应该模拟所有外部服务:参见phpunit模拟文档https://phpunit.de/manual/current/en/test-doubles.html#test-doubles.mock-objects
例如,如果你的类看起来像这样:
<?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);
}
...
}
有更容易使用的模拟框架,如预言(https://github.com/phpspec/prophecy),但它们可能需要更多的时间来处理它们。