2015-08-25 16 views
22

Tôi đang cố gắng thử một php final class nhưng kể từ khi nó được khai báo final Tôi tiếp tục nhận được lỗi này:PHP Mocking cuối cùng Lớp

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

Liệu có cách nào để có được xung quanh final hành vi này chỉ dành riêng cho các bài kiểm tra đơn vị của tôi mà không giới thiệu bất kỳ khung công tác mới nào?

+1

Bạn có thể tạo một bản sao của lớp CUỐI CÙNG đó không phải là CUỐI CÙNG và chế nhạo nó –

+1

@BryantFrankford nhờ cho các giải pháp . Trong khi điều này sẽ làm việc, lý tưởng nhất là tôi muốn tránh viết một lớp mới cho tình huống cụ thể này. Bạn sẽ không xảy ra để nhận thức được một giải pháp mà sẽ quy mô tốt hơn một chút? Nếu điều này trở nên gating cho dự án của tôi sau đó tôi sẽ thực hiện các giải pháp trên – DanHabib

+1

Khác với việc thay đổi lớp gốc để không được cuối cùng, cá nhân tôi không có bất kỳ giải pháp khác. –

Trả lời

13

Vì bạn đã đề cập bạn không muốn sử dụng bất kỳ khung công tác nào khác, bạn chỉ là leavin g cho mình một tùy chọn: uopz

uopz là một phần mở rộng ma thuật đen của thể loại runkit và đáng sợ, nhằm hỗ trợ hạ tầng QA.

uopz_flags là một hàm có thể sửa đổi cờ của hàm, phương thức và lớp.

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

sẽ mang lại

bool(false) 
7

tôi đề nghị bạn để có một cái nhìn tại các mockery testing framework rằng có một cách giải quyết cho tình huống này được mô tả trong trang: 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.

Một ví dụ giấy phép này để làm một cái gì đó như thế này:

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

Tôi không biết bạn cần làm gì nhưng tôi hi vọng điều này sẽ giúp ích

2

cách vui :)

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

Tôi đã thực hiện cách tiếp cận @Vadym và cập nhật nó. Bây giờ tôi sử dụng nó để thử nghiệm thành công!

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

Trả lời muộn cho người đang tìm kiếm câu trả lời truy vấn giáo lý cụ thể này. Bạn không thể giả mạo Doctrine \ ORM \ Query vì khai báo "cuối cùng" của nó, nhưng nếu bạn nhìn vào mã lớp Query thì bạn sẽ thấy rằng lớp AbstractQuery mở rộng của nó và sẽ không có bất kỳ vấn đề nào nhạo báng nó.

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

Điều này phù hợp với lớp học mở rộng bản tóm tắt hoặc triển khai giao diện. Nếu bản thân lớp đó đang được xác định cuối cùng thì bạn sẽ phải sử dụng một trong các vòng làm việc khác. – b01

0

Tôi tình cờ gặp vấn đề tương tự với Doctrine\ORM\Query. Tôi cần đơn vị kiểm tra mã sau:

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

    $result = $query->getOneOrNullResult(); 

    ... 

} 

createQuery trả về Doctrine\ORM\Query đối tượng. Tôi không thể sử dụng Doctrine\ORM\AbstractQuery cho mô hình của mình vì nó không có phương thức setMaxResults và tôi không muốn giới thiệu bất kỳ khung công tác nào khác. Để khắc phục hạn chế final trên lớp tôi sử dụng anonymous classes trong PHP 7, đó là siêu dễ dàng để tạo.Trong thử nghiệm trường hợp lớp của tôi, tôi có:

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

Sau đó, trong thử nghiệm của tôi:

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

    ... 

} 
Các vấn đề liên quan