Intereting Posts
Результаты базы данных как объекты или массивы? Как использовать PHP для проверки того, что каталог пуст? Mailjet: добавление электронной почты в список не работает Как преобразовать строки в хэш-код html-кода? как я могу сопоставить две строки, даже если они отличаются 1 символом? Nginx + php-fpm на Amazon Linux = вышел из сигнала 11 В чем преимущества использования Node.js vs PHP Что такое клонирование объектов в php? композитор работает везде, кроме одного проекта php объединяет 2 массива в один ассоциативный массив PHP Не удается выполнить Pytesseract в Python через shell_exec () Как избежать повторной отправки данных при обновлении в php Многие строки базы данных против одной строки разделенных запятыми строк Вызов функций PHP внутри строк HEREDOC Ошибка «Только оригинальный поток, создавший иерархию представлений, может коснуться его представлений» в Android

PHP Mocking Final Class

Я пытаюсь высмеять final class php, но поскольку он объявлен final я продолжаю получать эту ошибку:

PHPUnit_Framework_Exception: Class "Doctrine\ORM\Query" is declared "final" and cannot be mocked.

Нужно ли вообще обойти это final поведение только для моих модульных тестов без введения каких-либо новых фреймворков?

Поскольку вы упомянули, что не хотите использовать какие-либо другие рамки, вы оставите только один вариант: uopz

uopz – это черное магическое расширение жанра runkit-and-scary-stuff, предназначенного для поддержки инфраструктуры QA.

uopz_flags – это функция, которая может изменять флаги функций, методов и классов.

 <?php final class Test {} /** ZEND_ACC_CLASS is defined as 0, just looks nicer ... **/ uopz_flags(Test::class, null, ZEND_ACC_CLASS); $reflector = new ReflectionClass(Test::class); var_dump($reflector->isFinal()); ?> 

Будет давать

 bool(false) 

Я предлагаю вам взглянуть на фреймворк тестирования, в котором есть обходной путь для этой ситуации, описанной на странице: Работа с конечными классами / методами :

Вы можете создать прокси-макет, передав экземпляр объекта, который вы хотите высмеять в \ Mockery :: mock (), то есть Mockery затем сгенерирует прокси-сервер для реального объекта и выборочно перехватывает вызовы методов для целей установки и удовлетворения ожиданий.

В качестве примера это позволяет сделать что-то вроде этого:

 class MockFinalClassTest extends \PHPUnit_Framework_TestCase { public function testMock() { $em = \Mockery::mock("Doctrine\ORM\EntityManager"); $query = new Doctrine\ORM\Query($em); $proxy = \Mockery::mock($query); $this->assertNotNull($proxy); $proxy->setMaxResults(4); $this->assertEquals(4, $query->getMaxResults()); } - class MockFinalClassTest extends \PHPUnit_Framework_TestCase { public function testMock() { $em = \Mockery::mock("Doctrine\ORM\EntityManager"); $query = new Doctrine\ORM\Query($em); $proxy = \Mockery::mock($query); $this->assertNotNull($proxy); $proxy->setMaxResults(4); $this->assertEquals(4, $query->getMaxResults()); } 

Я не знаю, что вам нужно сделать, но я надеюсь, что эта помощь

Поздний ответ для тех, кто ищет ответ на конкретный запрос доктрины.

Вы не можете издеваться над Doctrine \ ORM \ Query, потому что его «окончательное» объявление, но если вы посмотрите на код класса Query, вы увидите, что его расширяющий класс AbstractQuery и не должно быть никаких проблем, издеваясь над ним.

 /** @var \PHPUnit_Framework_MockObject_MockObject|AbstractQuery $queryMock */ $queryMock = $this ->getMockBuilder('Doctrine\ORM\AbstractQuery') ->disableOriginalConstructor() ->setMethods(['getResult']) ->getMockForAbstractClass(); 

Смешной путь 🙂

PHP7.1, PHPUnit5.7

 <?php use Doctrine\ORM\Query; //... $originalQuery = new Query($em); $allOriginalMethods = get_class_methods($originalQuery); // some "unmockable" methods will be skipped $skipMethods = [ '__construct', 'staticProxyConstructor', '__get', '__set', '__isset', '__unset', '__clone', '__sleep', '__wakeup', 'setProxyInitializer', 'getProxyInitializer', 'initializeProxy', 'isProxyInitialized', 'getWrappedValueHolderValue', 'create', ]; // list of all methods of Query object $originalMethods = []; foreach ($allOriginalMethods as $method) { if (!in_array($method, $skipMethods)) { $originalMethods[] = $method; } } // Very dummy mock $queryMock = $this ->getMockBuilder(\stdClass::class) ->setMethods($originalMethods) ->getMock() ; foreach ($originalMethods as $method) { // skip "unmockable" if (in_array($method, $skipMethods)) { continue; } // mock methods you need to be mocked if ('getResult' == $method) { $queryMock->expects($this->any()) ->method($method) ->will($this->returnCallback( function (...$args) { return []; } ) ); continue; } // make proxy call to rest of the methods $queryMock->expects($this->any()) ->method($method) ->will($this->returnCallback( function (...$args) use ($originalQuery, $method, $queryMock) { $ret = call_user_func_array([$originalQuery, $method], $args); // mocking "return $this;" from inside $originalQuery if (is_object($ret) && get_class($ret) == get_class($originalQuery)) { if (spl_object_hash($originalQuery) == spl_object_hash($ret)) { return $queryMock; } throw new \Exception( sprintf( 'Object [%s] of class [%s] returned clone of itself from method [%s]. Not supported.', spl_object_hash($originalQuery), get_class($originalQuery), $method ) ); } return $ret; } )) ; } return $queryMock; 

Я реализовал подход @Vadym и обновил его. Теперь я использую его для тестирования!

 protected function getFinalMock($originalObject) { if (gettype($originalObject) !== 'object') { throw new \Exception('Argument must be an object'); } $allOriginalMethods = get_class_methods($originalObject); // some "unmockable" methods will be skipped $skipMethods = [ '__construct', 'staticProxyConstructor', '__get', '__set', '__isset', '__unset', '__clone', '__sleep', '__wakeup', 'setProxyInitializer', 'getProxyInitializer', 'initializeProxy', 'isProxyInitialized', 'getWrappedValueHolderValue', 'create', ]; // list of all methods of Query object $originalMethods = []; foreach ($allOriginalMethods as $method) { if (!in_array($method, $skipMethods)) { $originalMethods[] = $method; } } $reflection = new \ReflectionClass($originalObject); $parentClass = $reflection->getParentClass()->name; // Very dummy mock $mock = $this ->getMockBuilder($parentClass) ->disableOriginalConstructor() ->setMethods($originalMethods) ->getMock(); foreach ($originalMethods as $method) { // skip "unmockable" if (in_array($method, $skipMethods)) { continue; } // make proxy call to rest of the methods $mock ->expects($this->any()) ->method($method) ->will($this->returnCallback( function (...$args) use ($originalObject, $method, $mock) { $ret = call_user_func_array([$originalObject, $method], $args); // mocking "return $this;" from inside $originalQuery if (is_object($ret) && get_class($ret) == get_class($originalObject)) { if (spl_object_hash($originalObject) == spl_object_hash($ret)) { return $mock; } throw new \Exception( sprintf( 'Object [%s] of class [%s] returned clone of itself from method [%s]. Not supported.', spl_object_hash($originalObject), get_class($originalObject), $method ) ); } return $ret; } )); } return $mock; } 

Я наткнулся на ту же проблему с Doctrine\ORM\Query . Мне нужно было протестировать следующий код:

 public function someFunction() { // EntityManager was injected in the class $query = $this->entityManager ->createQuery('SELECT t FROM Test t') ->setMaxResults(1); $result = $query->getOneOrNullResult(); ... } 

createQuery возвращает createQuery Doctrine\ORM\Query . Я не мог использовать Doctrine\ORM\AbstractQuery для моего макета, потому что у него нет метода setMaxResults и я не хотел вводить какие-либо другие фреймворки. Чтобы преодолеть final ограничение для класса, я использую анонимные классы в PHP 7, которые очень легко создавать. В моем классе тестов у меня есть:

 private function getMockDoctrineQuery($result) { $query = new class($result) extends AbstractQuery { private $result; /** * Overriding original constructor. */ public function __construct($result) { $this->result = $result; } /** * Overriding setMaxResults */ public function setMaxResults($maxResults) { return $this; } /** * Overriding getOneOrNullResult */ public function getOneOrNullResult($hydrationMode = null) { return $this->result; } /** * Defining blank abstract method to fulfill AbstractQuery */ public function getSQL(){} /** * Defining blank abstract method to fulfill AbstractQuery */ protected function _doExecute(){} }; return $query; } 

Тогда в моем тесте:

 public function testSomeFunction() { // Mocking doctrine Query object $result = new \stdClass; $mockQuery = $this->getMockQuery($result); // Mocking EntityManager $entityManager = $this->getMockBuilder(EntityManagerInterface::class)->getMock(); $entityManager->method('createQuery')->willReturn($mockQuery); ... }