unit test querybuilder kerneltestcase app php unit-testing doctrine2 mocking phpunit

php - querybuilder - symfony test app



PHP Mocking Final Class (7)

Estoy intentando burlarme de una final class php final class pero como se declara final , sigo recibiendo este error:

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

¿Hay alguna forma de sortear este comportamiento final solo para mis pruebas de unidad sin introducir ningún marco nuevo?


Como mencionó que no desea utilizar ningún otro marco, solo se está dejando una opción: uopz

uopz es una extensión de magia negra del género runkit-and-scary-stuff, destinado a ayudar con la infraestructura de control de calidad.

uopz_flags es una función que puede modificar los indicadores de funciones, métodos y clases.

<?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()); ?>

Rendirá

bool(false)


Hay una pequeña biblioteca para pasar por alto las Finales exactamente para tal propósito. Descrito en detalle por blog .

Lo único que tienes que hacer es habilitar esta utilidad antes de que se carguen las clases:

DG/BypassFinals::enable();


He implementado el enfoque @Vadym y lo actualicé. Ahora lo uso para probar con éxito!

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


Le sugiero que eche un vistazo al marco de pruebas de burla que tiene una solución para esta situación descrita en la página: Tratar con las clases / métodos finales :

Puede crear un simulacro de proxy pasando el objeto instanciado que desea simular en / Mockery :: mock (), es decir, Mockery generará un Proxy al objeto real e interceptará selectivamente las llamadas de método con el fin de establecer y cumplir con las expectativas.

Como ejemplo este permite hacer algo como esto:

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

No sé lo que tienes que hacer, pero espero que esta ayuda


Manera graciosa :)

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;


Me encontré con el mismo problema con Doctrine/ORM/Query . Necesitaba probar unitariamente el siguiente código:

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

createQuery devuelve el objeto Doctrine/ORM/Query . No pude usar Doctrine/ORM/AbstractQuery para mi simulacro porque no tiene el método setMaxResults y no quería introducir ningún otro framework. Para superar la restricción final en la clase, uso clases anónimas en PHP 7, que son muy fáciles de crear. En mi clase de caso de prueba tengo:

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

Luego en mi prueba:

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


Respuesta tardía para alguien que está buscando esta consulta específica de doctrina respuesta simulada.

No puedes simular Doctrine / ORM / Query porque es su declaración "final", pero si miras el código de la clase Query, verás que se está extendiendo la clase AbstractQuery y no debería haber ningún problema en burlarse de él.

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