Symfony2:原则:PHPUnit:在单元测试中使用模拟实体管理器刷新时设置实体Id



Symfony 2.8.13/条令ORM 2.5.5/PHPUnit 5.7.5

我想测试一个使用条令实体管理器的类的方法。这个公共方法调用一个私有方法,该方法实例化Bookmark实体,刷新它并返回这个实体。然后,在测试的方法中,我需要访问实体Id。除了Bookmark实体本身之外,所有东西都被嘲笑了。主要问题是在我的实体中没有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-使用此处建议的反射类:

$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()方法。然后创建这个类的mock,并用这个方法替换和自定义从ReturnCallback方法获得的mock?看起来糟透了。。。

有什么想法吗?谢谢你的帮助。

反射看起来很有趣,但它降低了测试的可读性(与mock混合会使情况变得困难)。

我会为实体管理器创建一个假的,并在那里实现基于反射的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,情况可能会更困难。然后,例如,您可以在persist调用时增加$primaryIdForPersitingObject

public function persist($object)
{
$reflectionClass = new ReflectionClass(get_class($object));
$idProperty = $reflectionClass->getProperty('id');
$idProperty->setAccessible(true);
$idProperty->setValue($object, $this->primaryIdForPersitingObject);
$this->primaryIdForPersitingObject++;
}

它可以进一步扩展为每个实体类都有单独的primaryIdForPersingObject,并且您的测试仍然是干净的。

最新更新