2015-08-25 3 views
22

Ich versuche, ein PHP-final class zu verspotten, aber da es final deklariert ich immer erhalten diesen Fehler:PHP Mocking Abschlussklasse

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

Gibt es trotzdem, um dieses final Verhalten zu bekommen nur für meine Unit-Tests ohne neue Frameworks einzuführen?

+1

Sie können eine Kopie der FINAL Klasse erstellen, die nicht endgültig ist und verspotten es –

+1

@BryantFrankford Dank für die Lösung . Während dies funktionieren würde, würde ich es am liebsten vermeiden, eine neue Klasse für diese spezielle Situation zu schreiben. Sie würden sich nicht einer Lösung bewusst sein, die ein wenig besser skalieren würde? Wenn dies zu meinem Projekt wird, dann werde ich die obige Lösung implementieren – DanHabib

+1

Anders als die ursprüngliche Klasse zu ändern, um nicht endgültig zu sein, habe ich persönlich keine andere Lösung. –

Antwort

13

Da Sie erwähnt, dass Sie kein anderes Framework verwenden möchten, sind Sie nur leavin g dir eine Option: uopz

uopz ist eine schwarzmagische Erweiterung des runkit-and-scary-stuff-Genres, die bei der QA-Infrastruktur helfen soll.

uopz_flags ist eine Funktion, die die Flags von Funktionen, Methoden und Klassen ändern kann.

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

Wird

bool(false) 
7

Ich schlage vor, Sie am mockery testing framework einen Blick zu nehmen, die eine Abhilfe für diese Situation auf der Seite beschrieben haben: 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.

Als Beispiel dieser Genehmigung, so etwas zu tun:

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

Ich weiß nicht, was Sie tun müssen, aber ich hoffe, diese Hilfe

2

Lustige Weise ergeben :)

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

I @Vadym Ansatz und aktualisiert es implementiert haben. Jetzt benutze ich es zum Testen erfolgreich!

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

Späte Antwort für jemanden, der nach dieser spezifischen Doktrinabfrage Mock-Antwort sucht.

Sie können Doctrine \ ORM \ Query nicht wegen ihrer "finalen" Deklaration verspotten, aber wenn Sie in den Query-Klassencode schauen, werden Sie sehen, dass die abstrakteQuery-Klasse erweitert wird und es keine Probleme geben sollte.

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

Dies funktioniert für eine Klasse, die als final bezeichnet wird Erweitern Sie eine Zusammenfassung oder implementieren Sie eine Schnittstelle. Wenn die Klasse selbst als final definiert wird, müssen Sie eine der anderen Work-a-Runden verwenden. – b01

0

Ich stolperte über das gleiche Problem mit Doctrine\ORM\Query. Ich musste Unit-Test den folgenden Code:

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

    $result = $query->getOneOrNullResult(); 

    ... 

} 

createQuery kehrt Doctrine\ORM\Query Objekt. Ich konnte Doctrine\ORM\AbstractQuery nicht für meinen Schein verwenden, weil es setMaxResults Methode nicht hat und ich nicht irgendwelche anderen Rahmen einführen wollte. Um die final Einschränkung der Klasse zu überwinden, verwende ich anonymous classes in PHP 7, die super einfach zu erstellen sind.In meinem Testfall-Klasse habe ich:

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

Dann in meinem 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); 

    ... 

}