2017-02-09 2 views
0

Symfony 2.8.13/Doctrine ORM 2.5.5/PHPUnit 5.7.5Symfony2: Учение: PHPUnit: Установить идентификатор объекта при промывке издевались менеджера объекта в блоке тестов

Я хочу проверить метод класса, который использует администратор сущности доктрины. Этот открытый метод вызывает частный, который создает экземпляр объекта Bookmark, очищает его и возвращает этот объект. Затем в тестируемом методе мне нужно получить доступ к идентификатору объекта. Все издевается, за исключением самого объекта 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); 
     //... 
    } 
} 

Solutions?

1- использование Make класса отражения, как предложено 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, с этим? Кажется, crappy ...

Любые мысли? Спасибо за вашу помощь.

+0

Я знаю, что отражение должно быть злым, но я думаю, что метод отражения в вас упоминается как лучший вариант. Что-то кажется неправильным в создании классов для тестирования, таких как TestBookmark или MockEntityManager. Если вы собираетесь делать это регулярно в тестах, я нашел создание ReflectionSetterTrait, у которого есть только метод 'setProperty ($ object, $ property, $ value)', который будет полезен. – mickadoo

+0

@mickadoo Я согласен, черта - хороший вариант. Для диспетчера сущностей я не создаю определенный класс, я просто издеваюсь над ним с помощью PHPUnit mock builder. Во всяком случае, кажется, что отражение в этом случае является решением. – Cruz

ответ

1

Отражение выглядит интересным, но оно уменьшает читаемость тестов (смешение с макетами делает ситуацию сложной).

Я хотел бы создать фальшивку для менеджера сущности и реализует там устанавливающий идентификатор на основе отражения:

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

Конечно, ситуация может быть сложнее, если есть необходимость установки различных идентификаторов для многих сохраняющихся объектов. После этого вы можете, например, увеличить $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 каждого лица, и ваши тесты будут по-прежнему чист.

+0

Спасибо за предложение. Это также хороший способ использования отражения.Трюк здесь заключается в правильном управлении идентификаторами для разных объектов. Может быть, массив key/value (entity/id), переданный конструктору менеджера сущностей, может упростить это. – Cruz

+0

@Cruz Да, он должен быть построен для ответа на нужды. Эта реализация - самая легкая, концепция, черновик, и это не сложно расширить. –