Symfony2:Doctrine:PHPUnit:在单元测试中使用模拟实体管理器刷新期间设置实体ID

时间:2017-02-09 09:41:09

标签: symfony doctrine-orm phpunit

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方法获得的那个?看起来很糟糕......

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

1 个答案:

答案 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,并且您的测试仍然是干净的。

相关问题