2013-09-01 26 views
25

Có thể tạo đối tượng giả với trình khởi tạo bị vô hiệu hóa và các thuộc tính được bảo vệ được định cấu hình thủ công không?phpunit - mockbuilder - đặt đối tượng thuộc tính bên trong đối tượng

Dưới đây là một ví dụ ngu ngốc:

class A { 
    protected $p; 
    public function __construct(){ 
     $this->p = 1; 
    } 

    public function blah(){ 
     if ($this->p == 2) 
      throw Exception(); 
    } 
} 

class ATest extend bla_TestCase { 
    /** 
     @expectedException Exception 
    */ 
    public function testBlahShouldThrowExceptionBy2PValue(){ 
     $mockA = $this->getMockBuilder('A') 
      ->disableOriginalConstructor() 
      ->getMock(); 
     $mockA->p=2; //this won't work because p is protected, how to inject the p value? 
     $mockA->blah(); 
    } 
} 

Vì vậy, tôi muốn tiêm giá trị p được bảo vệ, vì vậy tôi không thể. Tôi có nên xác định setter hoặc IoC, hoặc tôi có thể làm điều này với phpunit?

+1

Chỉ để lưu hồ sơ - nếu bạn đang thử nghiệm API không công khai, thì bạn đang làm sai. Thử nghiệm đơn vị là về hành vi thử nghiệm, không phải là thực hiện nội bộ. –

Trả lời

35

Bạn có thể làm cho công chúng bất động sản bằng cách sử dụng Reflection, và sau đó thiết lập các giá trị mong muốn:

$a = new A; 
$reflection = new ReflectionClass($a); 
$reflection_property = $reflection->getProperty('p'); 
$reflection_property->setAccessible(true); 

$reflection_property->setValue($a, 2); 

Dù sao trong ví dụ của bạn, bạn không cần phải thiết lập giá trị p cho ngoại lệ được nâng lên. Bạn đang sử dụng một mô hình để có thể kiểm soát hành vi đối tượng, mà không tính đến nội dung của nó.

Vì vậy, thay vì thiết p = 2 nên một ngoại lệ được nâng lên, bạn cấu hình các mô hình để nâng cao một ngoại lệ khi các phương pháp blah được gọi là:

$mockA = $this->getMockBuilder('A') 
     ->disableOriginalConstructor() 
     ->getMock(); 
$mockA->expects($this->any()) 
     ->method('blah') 
     ->will($this->throwException(new Exception)); 

ngoái, đó là lạ mà bạn đang chế giễu Một lớp học trong ATest. Bạn thường giả lập các phụ thuộc cần thiết bởi đối tượng bạn đang thử nghiệm.

Hy vọng điều này sẽ hữu ích.

+1

Lớp A không phải là hoàn toàn phụ thuộc tiêm, tôi tạo ra trường hợp mới của một số lớp trong constructor của nó ... Vì vậy, tôi cần phải ghi đè lên các nhà xây dựng để giả ra những trường hợp. Không phải là cách tiếp cận tốt nhất, tôi nghĩ rằng tôi sẽ sử dụng container tiêm phụ thuộc thay vì điều này. – inf3rno

+0

mã của bạn chắc chắn sẽ dễ kiểm tra hơn. Có một số lựa chọn cho việc thực hiện DI, nhưng đây là một lựa chọn thực sự đơn giản: http://pimple.sensiolabs.org/ – gontrollez

+2

Không sử dụng thùng chứa phụ thuộc tiêm trong thử nghiệm! Một bài kiểm tra đơn vị tốt chỉ kiểm tra một lớp, và TẤT CẢ các phụ thuộc được tiêm dưới dạng mocks được cấu hình đầy đủ. Nếu bạn không thể làm điều này, bạn có một kiến ​​trúc xấu cần được cải thiện. – Sven

10

nghĩ rằng tôi muốn rời khỏi một phương pháp helper tiện dụng mà có thể được nhanh chóng sao chép và dán ở đây:

/** 
* Sets a protected property on a given object via reflection 
* 
* @param $object - instance in which protected value is being modified 
* @param $property - property on instance being modified 
* @param $value - new value of the property being modified 
* 
* @return void 
*/ 
public function setProtectedProperty($object, $property, $value) 
{ 
    $reflection = new ReflectionClass($object); 
    $reflection_property = $reflection->getProperty($property); 
    $reflection_property->setAccessible(true); 
    $reflection_property->setValue($object, $value); 
} 
-1

Nó sẽ là tuyệt vời nếu mỗi codebase sử dụng DI và IoC, và không bao giờ làm những thứ như thế này:

public function __construct(BlahClass $blah) 
{ 
    $this->protectedProperty = new FooClass($blah); 
} 

Bạn có thể sử dụng giả lập BlahClass trong hàm tạo, chắc chắn, nhưng sau đó hàm tạo đặt thuộc tính được bảo vệ thành thứ mà bạn KHÔNG THỂ giả lập. Vì vậy, bạn có thể nghĩ "Vâng refactor các nhà xây dựng để có một FooClass thay vì một BlahClass, sau đó bạn không cần phải nhanh chóng FooClass trong constructor, và bạn có thể đưa vào một mô hình thay vì!" Vâng, bạn sẽ đúng, nếu điều đó không có nghĩa là bạn sẽ phải thay đổi mọi cách sử dụng của lớp trong toàn bộ codebase để cho nó một FooClass thay vì BlahClass.

Không phải mọi codebase đều hoàn hảo và đôi khi bạn chỉ cần hoàn thành công việc. Và điều đó có nghĩa là, có, đôi khi bạn cần phải phá vỡ quy tắc "chỉ kiểm tra API công khai".

+1

-> disableOriginalConstructor? –

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