2013-09-01 4 views
25

Возможно ли создать макет объекта с отключенным конструктором и вручную настроенными защищенными свойствами?phpunit - mockbuilder - установить внутреннее свойство mock object

Вот идиотский пример:

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

Так что я хочу придать значение р, который защищен, так что я не могу. Должен ли я определить setter или IoC, или я могу сделать это с помощью phpunit?

+1

Только для записи - если вы тестируете непубличный API, то вы делаете это неправильно. Модульное тестирование - это поведение тестирования, а не внутренняя реализация. –

ответ

35

Вы можете сделать публичную собственность с помощью отражения, а затем установите нужное значение:

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

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

Во всяком случае, в вашем примере вам не нужно устанавливать значение р для исключения быть поднято. Вы используете макет для того, чтобы иметь возможность контролировать поведение объекта, не принимая во внимание его внутренности.

Таким образом, вместо того, чтобы р = 2, так возникает исключение, настроить макет поднять исключение, когда метод мля называется:

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

Последнее, странно, что вы насмешливый Класс в ATest. Обычно вы издеваетесь над зависимостями, которые требуется тестировать.

Надеюсь, это поможет.

+1

Класс A не является полностью зависимой инъекцией, я создаю новый экземпляр нескольких классов в его конструкторе ... Поэтому мне нужно переопределить конструктор, чтобы издеваться над этими экземплярами. Не лучший подход, я думаю, что вместо этого я буду использовать контейнер для инъекций зависимостей. – inf3rno

+0

Ваш код будет определенно более проверяемым. Существует несколько вариантов реализации DI, но это очень простой: http://pimple.sensiolabs.org/ – gontrollez

+2

Не используйте контейнер для тестирования зависимостей в тесте! Хороший модульный тест проверяет только один класс, а зависимости ALL - как полностью настроенные mocks. Если вы не можете этого сделать, у вас будет плохая архитектура, которая должна быть улучшена. – Sven

10

Мысль я бы оставить удобный вспомогательный метод, который можно было бы быстро скопировать и вставить здесь:

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

Было бы удивительно, если каждый кодовая используется DI и IoC, и никогда не делал вещи, как это:

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

Возможно, вы можете использовать макет BlahClass в конструкторе, но тогда конструктор устанавливает защищенное свойство тому, что вы НЕ МОЖЕТЕ имитировать.

Итак, вы, вероятно, думаете: «Хорошо реорганизуйте конструктор, чтобы взять FooClass вместо BlahClass, тогда вам не нужно создавать экземпляр FooClass в конструкторе, и вы можете добавить в него макет!» Ну, вы были бы правы, если бы это не означало, что вам придется менять каждое использование класса во всей кодовой базе, чтобы дать ему FooClass вместо BlahClass.

Не все кодовые коды совершенны, и иногда вам просто нужно что-то сделать. И это означает, что да, иногда вам нужно нарушить правило «только проверять публичные API».

+1

-> disableOriginalConstructor? –