使用PHPUnit和Doctrine我经常最终编写非常大的方法来模拟Doctrines ClassMetadata
,虽然在我看来它不需要被嘲笑,因为它可以看作是稳定的。我还是需要嘲笑EntityManager
,因为我不想让Doctrine连接到数据库。
所以我的问题是:如何在不需要数据库连接的情况下通过ClassMetadata
模拟获取EntityManager
?对于所有最终的数据库调用,EntityManager
仍然需要是模拟,我只是不想再次写下我的所有元数据。
我使用DoctrineModule
作为Zend 2,因此能够使用我的配置获取Metadata
对象会很有用,但我认为它也可以手动阅读所需的部分。
示例:
public function testGetUniqueFields()
{
$this->prepareGetUniqueFields(); // about 50 lines of mocking ClassMetadata
$entity = 'UniqueWithoutAssociation';
$unique = $this->handler->getUniqueFields($entity);
$expected = ["uniqueColumn"];
$this->assertEquals($expected, $unique,
'getUniqueFields does not return the unique fields');
}
实际班级的代码:
public function getUniqueFields($class)
{
$unique = array();
$metadata = $this->getClassMetadata($class);
$fields = $metadata->getFieldNames();
foreach ($fields as $field) {
if($metadata->isUniqueField($field) && !$metadata->isIdentifier($field)) {
$unique[] = $field;
}
}
return $unique;
}
测试按预期工作,但每次我测试另一种方法或方法的其他行为时,我需要再次准备模拟或结合过去的定义。此外,此代码所需的50行是此测试中最少的行。大多数测试类都是关于ClassMetadata
模拟的。这是一个耗时的工作 - 如果您将ClassMetadata
视为一个稳定的组件 - 那就是不必要的工作。
答案 0 :(得分:2)
在花了很多时间主演Doctrine源代码之后,我找到了一个解决方案。
再一次,这个解决方案只有在您经常使用Doctrines ClassMetadata
对象时才会模拟每个方法调用变得不干净。在其他所有情况下,您仍应创建ClassMetadata
的模拟。
尽管如此,由于作曲家的最小稳定性设置被设置为稳定,因此可以看出这些组件是稳定的,因此不需要创建模拟对象。
ClassMetadata
取决于其他几个Doctrine
类,这些类都是通过无处不在的EntityManager
注入的:
Doctrine\ORM\Configuration
获取实体路径
Doctrine\Common\Annotations\AnnotationReader
和Doctrine\ORM\Mapping\Driver\AnnotationDriver
通过Configuration
对象 Doctrine\DBAL\Connection
获取数据库平台以了解标识符策略。应该模拟此对象,以便不可能进行数据库调用
Doctrine\DBAL\Platforms\AbstractPlatform
如上所述 Doctrine\Common\EventManager
触发某些事件
对于单个测试方法或简单方法调用,我创建了一个方法,返回一个能够返回有效EntityManager
对象的ClassMetadata
模拟对象:
/**
* @return EntityManager|\PHPUnit_Framework_MockObject_MockObject
*/
public function getEmMock()
{
$dir = __DIR__."/Asset/Entity/";
$config = Setup::createAnnotationMetadataConfiguration(array($dir), true);
$eventManager = new \Doctrine\Common\EventManager();
$platform = new PostgreSqlPlatform();
$metadataFactory = new ClassMetadataFactory();
$config->setMetadataDriverImpl(new AnnotationDriver(new AnnotationReader()));
$connectionMock = $this->getMockBuilder('Doctrine\DBAL\Connection')
->disableOriginalConstructor()
->getMock();
$connectionMock->expects($this->any())
->method('getDatabasePlatform')
->will($this->returnValue($platform));
/** @var EntityManager|\PHPUnit_Framework_MockObject_MockObject $emMock */
$emMock = $this->getMockBuilder('Doctrine\ORM\EntityManager')
->disableOriginalConstructor()
->getMock();
$metadataFactory->setEntityManager($emMock);
$emMock->expects($this->any())
->method('getConfiguration')
->will($this->returnValue($config));
$emMock->expects($this->any())
->method('getConnection')
->will($this->returnValue($connectionMock));
$emMock->expects($this->any())
->method('getEventManager')
->will($this->returnValue($eventManager));
$emMock->expects($this->any())
->method('getClassMetadata')
->will($this->returnCallback(function($class) use ($metadataFactory){
return $metadataFactory->getMetadataFor($class);
}));
return $emMock;
}
在这里,您甚至可以通过调用为EntityManager
模拟创建的getter来操纵所有对象。但这并不完全是干净的,并且在某些情况下该方法仍然不灵活。仍然是一个简单的解决方案,你可以添加一些参数并将该方法放入特征中以重复使用它。
为了满足更多需求,我创建了一个抽象类,它提供了最大的灵活性,允许您模拟其他所有内容或以其他方式创建一些组件。
它需要两种配置:实体路径和平台对象。您可以通过在setUp
方法中设置对象来操纵或替换任何对象,然后使用EntityManager
获取所需的getEmMock()
模拟。
稍大一些,但现在是:
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\ORM\Tools\Setup;
/**
* Class AbstractTestWithMetadata
* @author Marius Teller
*/
abstract class AbstractTestWithMetadata extends \PHPUnit_Framework_TestCase
{
const EXCEPTION_NO_ENTITY_PATHS_SET = "At least one entity path must be set";
const EXCEPTION_NO_PLATFORM_SET = "An instance of Doctrine\\DBAL\\Platforms\\AbstractPlatform must be set";
/**
* @var array
*/
protected $entityPaths = [];
/**
* @var AbstractPlatform
*/
protected $platform;
/**
* @var EntityManager
*/
protected $emMock;
/**
* @var Connection
*/
protected $connectionMock;
/**
* @var Configuration
*/
protected $configuration;
/**
* @var EventManager
*/
protected $eventManager;
/**
* @var ClassMetadataFactory
*/
protected $classMetadataFactory;
/**
* @return array
* @throws \Exception
*/
public function getEntityPaths()
{
if($this->entityPaths === []) {
throw new \Exception(self::EXCEPTION_NO_ENTITY_PATHS_SET);
}
return $this->entityPaths;
}
/**
* @param array $entityPaths
*/
public function setEntityPaths(array $entityPaths)
{
$this->entityPaths = $entityPaths;
}
/**
* add an entity path
* @param string $path
*/
public function addEntityPath($path)
{
$this->entityPaths[] = $path;
}
/**
* @return AbstractPlatform
* @throws \Exception
*/
public function getPlatform()
{
if(!isset($this->platform)) {
throw new \Exception(self::EXCEPTION_NO_PLATFORM_SET);
}
return $this->platform;
}
/**
* @param AbstractPlatform $platform
*/
public function setPlatform(AbstractPlatform $platform)
{
$this->platform = $platform;
}
/**
* @return EntityManager
*/
public function getEmMock()
{
if(!isset($this->emMock)) {
/** @var EntityManager|\PHPUnit_Framework_MockObject_MockObject $emMock */
$emMock = $this->getMockBuilder('Doctrine\ORM\EntityManager')
->disableOriginalConstructor()
->getMock();
$config = $this->getConfiguration();
$connectionMock = $this->getConnectionMock();
$eventManager = $this->getEventManager();
$classMetadataFactory = $this->getClassMetadataFactory();
$classMetadataFactory->setEntityManager($emMock);
$emMock->expects($this->any())
->method('getConfiguration')
->will($this->returnValue($config));
$emMock->expects($this->any())
->method('getConnection')
->will($this->returnValue($connectionMock));
$emMock->expects($this->any())
->method('getEventManager')
->will($this->returnValue($eventManager));
$emMock->expects($this->any())
->method('getClassMetadata')
->will($this->returnCallback(function($class) use ($classMetadataFactory){
return $classMetadataFactory->getMetadataFor($class);
}));
$this->setEmMock($emMock);
}
return $this->emMock;
}
/**
* @param EntityManager $emMock
*/
public function setEmMock($emMock)
{
$this->emMock = $emMock;
}
/**
* @return Connection
*/
public function getConnectionMock()
{
if(!isset($this->connectionMock)) {
$platform = $this->getPlatform();
/** @var Connection|\PHPUnit_Framework_MockObject_MockObject $connectionMock */
$connectionMock = $this->getMockBuilder('Doctrine\DBAL\Connection')
->disableOriginalConstructor()
->getMock();
$connectionMock->expects($this->any())
->method('getDatabasePlatform')
->will($this->returnValue($platform));
$this->setConnectionMock($connectionMock);
}
return $this->connectionMock;
}
/**
* @param Connection $connectionMock
*/
public function setConnectionMock($connectionMock)
{
$this->connectionMock = $connectionMock;
}
/**
* @return Configuration
*/
public function getConfiguration()
{
if(!isset($this->configuration)) {
$config = Setup::createAnnotationMetadataConfiguration($this->getEntityPaths(), true);
$config->setMetadataDriverImpl(new AnnotationDriver(new AnnotationReader()));
$this->setConfiguration($config);
}
return $this->configuration;
}
/**
* @param Configuration $configuration
*/
public function setConfiguration(Configuration $configuration)
{
$this->configuration = $configuration;
}
/**
* @return EventManager
*/
public function getEventManager()
{
if(!isset($this->eventManager)) {
$this->setEventManager(new EventManager());
}
return $this->eventManager;
}
/**
* @param EventManager $eventManager
*/
public function setEventManager($eventManager)
{
$this->eventManager = $eventManager;
}
/**
* @return ClassMetadataFactory
*/
public function getClassMetadataFactory()
{
if(!isset($this->classMetadataFactory)) {
$this->setClassMetadataFactory(new ClassMetadataFactory());
}
return $this->classMetadataFactory;
}
/**
* @param ClassMetadataFactory $classMetadataFactory
*/
public function setClassMetadataFactory(ClassMetadataFactory $classMetadataFactory)
{
$this->classMetadataFactory = $classMetadataFactory;
}
}
还有一个提示:您可能会遇到其他类注释的问题,例如: Zend\Form\Annotation\Validator
。这样的注释将在Doctrines解析器中引发异常,因为此解析器不使用自动加载,只检查已加载的类。因此,如果您仍想使用它们,则只需在解析类注释之前手动包含它们。