Symfony2:Doctrine:PHPUnit:在单元测试中使用模拟实体管理器刷新期间设置实体 ID [英] Symfony2 : Doctrine : PHPUnit : Set entity Id during flushing with mocked entity manager in unit tests

查看:15
本文介绍了Symfony2:Doctrine:PHPUnit:在单元测试中使用模拟实体管理器刷新期间设置实体 ID的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Symfony 2.8.13 / Doctrine ORM 2.5.5 / PHPUnit 5.7.5

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 ?

Tested class and method

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;
    }
}

Test

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);
        //...
    }
}

Solutions ?

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- 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).

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() { }

    ...
}

Once you implemented this, you can inject the instance of MyEntityManager and make your tests small and easier to maintain.

You test would look like

<?php

class BookmarkManagerTest extends PHPUnit_Framework_TestCase
{
    public function testThatRestaurantAdditionToBookmarksIsWellManaged()
    {
        // ...
        $entityManager = MyEntityManager(1);
        //...
        $bookManager = new BookmarkManager($tokenStorageMock, $entityManager, $sessionMock);
        //...
    }
}

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++;
}

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屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆