2013-09-01 9 views
25

Czy jest możliwe utworzenie obiektu próbnego z wyłączonym konstruktorem i ręcznie ustawionymi właściwościami chronionymi?phpunit - mockbuilder - ustaw właściwość wewnętrzną obiektu próbnego

Oto idiotyczny przykład:

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

Więc chcę wstrzyknąć wartość P, która jest chroniona, więc nie mogę. Czy powinienem zdefiniować setter lub IoC, czy mogę to zrobić z phpunit?

+1

Tylko dla rekordu - jeśli testujesz niepubliczny interfejs API, robisz to źle. Testowanie jednostkowe dotyczy zachowania testowego, a nie implementacji wewnętrznej. –

Odpowiedz

35

Można dokonać własnością publiczną za pomocą refleksji, a następnie ustawić żądaną wartość:

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

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

Zresztą w przykładzie nie trzeba ustawić wartość p dla wyjątku zostać podniesiony. Używasz makiety, aby móc przejąć kontrolę nad zachowaniem obiektu, nie biorąc pod uwagę jego wewnętrznych elementów.

Więc zamiast ustawiania p = 2, więc jest wyjątek, skonfigurować makiety podnieść wyjątek, gdy metoda bla nazywa się:

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

Ostatni, że to dziwne, że jesteś drwi z Klasa w ATest. Zwykle kpisz z zależności wymaganych przez badany obiekt.

Mam nadzieję, że to pomoże.

+1

Klasa A nie jest w pełni zależna, ja tworzę nową instancję kilku klas w jego konstruktorze ... Tak więc muszę nadpisać konstruktora, aby wyłudzić te instancje. Nie najlepsze podejście, myślę, że zamiast tego użyję kontenera do iniekcji zależności. – inf3rno

+0

Twój kod będzie zdecydowanie bardziej sprawdzalny. Istnieje kilka opcji implementacji DI, ale jest to bardzo proste: http://pimple.sensiolabs.org/ – gontrollez

+2

Nie używaj pojemnika z wtyczkami zależności w teście! Dobry test jednostkowy testuje tylko jedną klasę, a wszystkie zależności są wstrzykiwane jako w pełni skonfigurowane makiety. Jeśli nie możesz tego zrobić, masz złą architekturę, którą należy poprawić. – Sven

10

myślałem, że zostawić poręczne metody pomocnika, który mógłby być szybko skopiować i wkleić tutaj:

/** 
* 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

To byłoby niesamowite, jeśli każdy codebase wykorzystywane DI i IoC i nigdy nie robił rzeczy tak:

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

Można użyć fałszywego BlahClassa w konstruktorze, oczywiście, ale wtedy konstruktor ustawia chronioną własność na coś, czego NIE MOŻNA kpić.

Więc prawdopodobnie myślisz: "Cóż, pomóż konstruktorowi wziąć FooClass zamiast BlahClass, wtedy nie musisz tworzyć instancji FooClass w konstruktorze, a zamiast tego możesz w zamian udawać!" Cóż, miałbyś rację, gdyby to nie oznaczało, że musiałbyś zmienić każde użycie klasy w całej bazie kodu, aby nadać mu FooClass zamiast BlahClass.

Nie każda baza kodów jest idealna, a czasami wystarczy, że wykonasz coś. Oznacza to, że czasami trzeba złamać regułę "tylko testuj publiczne interfejsy API".

+1

-> disableOriginalConstructor? –

Powiązane problemy