2015-08-25 21 views
22

Ja próbuje szydzić php final class ale ponieważ jest zadeklarowana final Ciągle odbiera ten błąd:PHP Mocking final class

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

Czy mimo to aby ominąć ten final zachowania tylko dla moich testów jednostkowych bez wprowadzania nowych ram?

+1

Można by utworzyć kopię klasy końcowych, że nie jest ostateczna i wyśmiewać go –

+1

@BryantFrankford dzięki za rozwiązanie . Chociaż to zadziała, najlepiej wolałbym uniknąć pisania nowej klasy dla tej konkretnej sytuacji. Czy nie zdajesz sobie sprawy z rozwiązania, które będzie trochę lepsze? Jeśli stanie się to bramką do mojego projektu, wtedy zaimplementuję powyższe rozwiązanie – DanHabib

+1

Oprócz zmiany pierwotnej klasy, aby nie była ostateczna, osobiście nie mam innego rozwiązania. –

Odpowiedz

13

Od czasu, gdy wspomniałeś, że nie chcesz używać innych ram, jesteś tylko leavin g sobie jedna opcja: uopz

Uopz to czarne magiczne rozszerzenie gatunku Runkit-and-scary-stuff, mające na celu pomoc w zakresie infrastruktury QA.

uopz_flags to funkcja, która może modyfikować flagi funkcji, metod i klas.

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

przyniesie

bool(false) 
7

Proponuję spojrzeć na mockery testing framework które mają obejście tej sytuacji opisanej na stronie: 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.

Jako przykład tego zezwolenia zrobić coś takiego:

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

Nie wiem, co trzeba zrobić, ale mam nadzieję, że ta pomoc będzie pomocna.

2

zabawny sposób :)

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 zostały wdrożone podejście @Vadym i aktualizowane go. Teraz używam go do testowania z powodzeniem!

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

Późna odpowiedź dla kogoś, kto szuka tej konkretnej kwerendy doktryny, pozornie odpowiada.

Nie możesz wyśmiać Doctrine \ ORM \ Query, ponieważ jest to "ostateczna" deklaracja, ale jeśli spojrzysz na kod klasy Query, zobaczysz, że jego rozszerzenie klasy AbstractQuery i nie powinno być żadnych problemów z kpiny z niego.

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

Działa to dla klasy oznaczonej jako ostateczna rozszerzać abstrakt lub implementować interfejs. Jeśli sama klasa zostanie zdefiniowana jako ostateczna, będziesz musiał użyć jednej z innych rund pracy. – b01

0

Natknąłem się na ten sam problem z Doctrine\ORM\Query. Musiałem testów jednostkowych następujący kod:

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

    $result = $query->getOneOrNullResult(); 

    ... 

} 

createQuery powraca Doctrine\ORM\Query obiektu. Nie mogłem użyć Doctrine\ORM\AbstractQuery dla mojej próby, ponieważ nie ma ona metody setMaxResults i nie chciałem wprowadzać żadnych innych frameworków. Aby pokonać ograniczenie final na klasie używam anonymous classes w PHP 7, które są bardzo łatwe do stworzenia.W moim przypadku testu klasie mam:

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

Wtedy w moim teście:

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

    ... 

} 
Powiązane problemy