2015-08-25 3 views
22

Sto tentando di prendere in giro un php final class ma dal momento che è dichiarata final Continuo a ricevere questo errore:PHP Mocking Finale Classe

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

Esiste un modo per aggirare questo final comportamento solo per i miei test di unità senza introdurre nuovi quadri?

+1

è possibile creare una copia della classe finale che non è definitivo e mock –

+1

@BryantFrankford grazie per la soluzione . Mentre ciò funzionerebbe, preferirei idealmente evitare di scrivere una nuova classe per questa specifica situazione. Non ti accorgerai di una soluzione che si ridimensionerà un po 'meglio? Se questo diventa il mio progetto allora implementerò la soluzione di cui sopra – DanHabib

+1

Oltre a cambiare la classe originale per non essere definitiva, personalmente non ho altra soluzione. –

risposta

13

Dato che hai menzionato che non vuoi usare nessun altro framework, sei solo leavin g una sola opzione: uopz

uopz è un'estensione magica nera del genere runkit-and-scary-robe, inteso come aiuto per l'infrastruttura di QA.

uopz_flags è una funzione che può modificare i flag di funzioni, metodi e classi.

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

produrrà

bool(false) 
7

vi consiglio di dare un'occhiata al mockery testing framework che hanno una soluzione per questa situazione descritta nella pagina: Dealing with Final Classes/Methods:

You can create a proxy mock by passing the instantiated object you wish to mock into \Mockery::mock(), i.e. Mockery will then generate a Proxy to the real object and selectively intercept method calls for the purposes of setting and meeting expectations.

Come ad esempio questo permesso di fare qualcosa di simile:

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

Non so cosa devi fare ma, spero che questo aiuti

2

modo divertente :)

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

Ho implementato approccio @Vadym ed aggiornato esso. Ora lo uso per testare con successo!

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

risposta tardiva per chi è alla ricerca di questo specifico risposta finta di query dottrina.

Non è possibile simulare Doctrine \ ORM \ Query perché la sua dichiarazione "finale", ma se si esamina il codice di classe di Query, si vedrà che la sua classe di estensione AbstractQuery e non ci dovrebbero essere problemi di derisione.

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

Questo funziona per una classe contrassegnata finale che estendere un abstract o implementare un'interfaccia. Se la classe stessa viene definita definitiva, dovrai utilizzare una delle altre soluzioni. – b01

0

Mi sono imbattuto nello stesso problema con Doctrine\ORM\Query. Avevo bisogno di unit test del codice seguente:

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

    $result = $query->getOneOrNullResult(); 

    ... 

} 

createQuery rendimenti Doctrine\ORM\Query oggetto. Non ho potuto utilizzare Doctrine\ORM\AbstractQuery per il mio simulato perché non ha il metodo setMaxResults e non ho voluto introdurre altri framework. Per superare la limitazione final sulla classe, utilizzo anonymous classes in PHP 7, che sono semplicissimi da creare.Nel mio test caso di classe che ho:

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

Poi, nel mio test:

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

    ... 

}