Symfony 2.8.13 / Doctrine ORM 2.5.5 / PHPUnit 5.7.5
我想测试一个使用doctrine实体管理器的类的方法。此公共方法调用实例化Bookmark实体的私有方法,将其刷新并返回此实体。然后,在测试方法中,我需要访问实体Id。一切都被嘲笑,除了书签实体本身。主要问题是我的实体中没有setId()方法。以下是解决此问题的代码和主要想法,但我不知道它是否正确?
经过测试的课程和方法
class BookmarkManager
{
//...
public function __construct(TokenStorageInterface $tokenStorage, ObjectManager $em, Session $session)
{
//...
}
public function manage($bookmarkAction, $bookmarkId, $bookmarkEntity, $bookmarkEntityId)
{
//...
$bookmark = $this->add($bookmarkEntity, $bookmarkEntityId);
//...
$bookmarkId = $bookmark->getId();
//...
}
private function add($entity, $entityId)
{
//...
$bookmark = new Bookmark();
//...
$this->em->persist($bookmark);
$this->em->flush();
return $bookmark;
}
}
测试
class BookmarkManagerTest extends \PHPUnit_Framework_TestCase
{
public function testThatRestaurantAdditionToBookmarksIsWellManaged()
{
//...
// THIS WON'T WORK AS NO setId() METHOD EXISTS
$entityManagerMock->expects($this->once())
->method('persist')
->will($this->returnCallback(function ($bookmark) {
if ($bookmark instanceof Bookmark) {
$bookmark->setId(1);
}
}));
//...
$bookManager = new BookmarkManager($tokenStorageMock, $entityManagerMock, $sessionMock);
//...
}
}
解决方案?
1-使用建议here的反射类:
$entityManagerMock->expects($this->once())
->method('persist')
->will($this->returnCallback(function ($bookmark) {
if ($bookmark instanceof Bookmark) {
$class = new \ReflectionClass($bookmark);
$property = $class->getProperty('id');
$property->setAccessible(true);
$property->setValue($bookmark, 1);
//$bookmark->setId(1);
}
}));
2-创建一个测试Boookmark实体,该实体从真实实体扩展并添加setId()方法。然后创建一个这个类的模拟,并用这个替换和自定义从ReturnCallback方法获得的那个?看起来很糟糕......
有什么想法?谢谢你的帮助。
答案 0 :(得分:1)
反射看起来很有趣,但它降低了测试的可读性(与模拟混合使情况变得困难)。
我会为实体管理器创建一个假的,并根据反射实现设置id:
class MyEntityManager implements ObjectManager
{
private $primaryIdForPersitingObject;
public function __construct($primaryIdForPersitingObject)
{
$this->primaryIdForPersitingObject = $primaryIdForPersitingObject;
}
...
public function persist($object)
{
$reflectionClass = new ReflectionClass(get_class($object));
$idProperty = $reflectionClass->getProperty('id');
$idProperty->setAccessible(true);
$idProperty->setValue($object, $this->primaryIdForPersitingObject);
}
public function flush() { }
...
}
实现此功能后,您可以注入MyEntityManager
的实例,并使您的测试变得更小,更易于维护。
你的测试看起来像
<?php
class BookmarkManagerTest extends \PHPUnit_Framework_TestCase
{
public function testThatRestaurantAdditionToBookmarksIsWellManaged()
{
// ...
$entityManager = MyEntityManager(1);
//...
$bookManager = new BookmarkManager($tokenStorageMock, $entityManager, $sessionMock);
//...
}
}
当然,如果需要为许多持久对象设置不同的ID,情况可能会更难。然后,您可以在$primaryIdForPersitingObject
来电
persist
public function persist($object)
{
$reflectionClass = new ReflectionClass(get_class($object));
$idProperty = $reflectionClass->getProperty('id');
$idProperty->setAccessible(true);
$idProperty->setValue($object, $this->primaryIdForPersitingObject);
$this->primaryIdForPersitingObject++;
}
可以进一步扩展为每个实体类都有单独的primaryIdForPersitingObject,并且您的测试仍然是干净的。