Symfony2 : Doctrine : PHPUnit : 在单元测试中使用模拟实体管理器刷新期间设置实体 Id [英] Symfony2 : Doctrine : PHPUnit : Set entity Id during flushing with mocked entity manager in unit tests
问题描述
Symfony 2.8.13/Doctrine ORM 2.5.5/PHPUnit 5.7.5
Symfony 2.8.13 / Doctrine ORM 2.5.5 / PHPUnit 5.7.5
我想测试一个使用了学说实体管理器的类的方法.这个公共方法调用一个私有方法来实例化一个 Bookmark 实体,刷新它并返回这个实体.然后,在经过测试的方法中,我需要访问实体 Id.除了 Bookmark 实体本身之外,所有东西都被模拟了.主要问题是我的实体中没有 setId() 方法.这是代码和我解决这个问题的主要想法,但我不知道它是否正确?
I want to test a method of a class that makes use of the doctrine entity manager. This public method calls a private one that instantiates a Bookmark entity, flushes it and returns this entity. Then later, in the tested method I need to access the entity Id. Everything is mocked excepted the Bookmark entity itself. The main problem is that there is no setId() method in my entity. Here is the code and my main idea to solve this issue but I don't know if it is correct ?
经过测试的类和方法
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- 按照此处的建议使用反射类:
1- Make usage of reflection class as proposed 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- 创建一个从真实实体扩展而来的测试书签实体,并添加一个 setId() 方法.然后创建一个这个类的模拟,并用这个替换和自定义从 ReturnCallback 方法获得的那个?好像很烂...
2- Create a test Boookmark entity that extends from the real one and add a setId() method. Then create a mock of this class and replace and customize the one got from the ReturnCallback method with this one ? It seems crappy...
有什么想法吗?感谢您的帮助.
Any thoughts ? Thanks for your help.
推荐答案
反射看起来很有趣,但它降低了测试的可读性(与模拟混合会使情况变得困难).
The reflection looks interesting but it decreases readability of tests (mixing with mocks makes the situation tough).
我会为实体管理器创建一个假的,并根据反射实现那里的设置 id:
I would create a fake for entity manager and implements there setting id based on reflection:
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
的实例,让你的测试更小更容易维护.
Once you implemented this, you can inject the instance of MyEntityManager
and make your tests small and easier to maintain.
你的测试看起来像
<?php
class BookmarkManagerTest extends \PHPUnit_Framework_TestCase
{
public function testThatRestaurantAdditionToBookmarksIsWellManaged()
{
// ...
$entityManager = MyEntityManager(1);
//...
$bookManager = new BookmarkManager($tokenStorageMock, $entityManager, $sessionMock);
//...
}
}
当然,如果需要为许多持久化对象设置不同的 id,情况可能会更困难.然后你可以,例如,在 persist
调用
Of course, a situation may be harder if there is a need of setting different ids for many persisting objects. Then you can, for example, increase $primaryIdForPersitingObject
on persist
call
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,并且您的测试将仍然是干净的.
It may be extended even further to have separate primaryIdForPersitingObject each entity class, and your tests will be still clean.
这篇关于Symfony2 : Doctrine : PHPUnit : 在单元测试中使用模拟实体管理器刷新期间设置实体 Id的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!