Возможно ли создать макет объекта с отключенным конструктором и вручную настроенными защищенными свойствами?
Вот идиотский пример:
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(); } }
Поэтому я хочу ввести значение p, которое защищено, поэтому я не могу. Должен ли я определить setter или IoC, или я могу сделать это с помощью phpunit?
Вы можете сделать свойство общедоступным с помощью Reflection, а затем установить желаемое значение:
$a = new A; $reflection = new ReflectionClass($a); $reflection_property = $reflection->getProperty('p'); $reflection_property->setAccessible(true); $reflection_property->setValue($a, 2);
Во всяком случае, в вашем примере вам не нужно устанавливать значение p для исключения Exception. Вы используете макет для того, чтобы иметь возможность контролировать поведение объекта, не принимая во внимание его внутренности.
Таким образом, вместо установки p = 2, так что возникает исключение, вы настраиваете макет для создания исключения, когда вызывается метод blah:
$mockA = $this->getMockBuilder('A') ->disableOriginalConstructor() ->getMock(); $mockA->expects($this->any()) ->method('blah') ->will($this->throwException(new Exception));
Наконец, странно, что вы издеваетесь над классом A в ATest. Обычно вы издеваетесь над зависимостями, которые требуется тестировать.
Надеюсь это поможет.
Думал, что я оставил бы удобный метод помощника, который можно быстро скопировать и вставить здесь:
/** * 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); }
Было бы удивительно, если бы каждая кодовая база использовала DI и IoC и никогда не делала таких вещей:
public function __construct(BlahClass $blah) { $this->protectedProperty = new FooClass($blah); }
Конечно, вы можете использовать mock BlahClass в конструкторе, но затем конструктор устанавливает защищенное свойство тому, что вы НЕ МОЖЕТЕ имитировать.
Таким образом, вы, вероятно, думаете: «Хорошо реорганизуйте конструктор, чтобы взять FooClass вместо BlahClass, тогда вам не нужно создавать экземпляр FooClass в конструкторе, и вместо этого вы можете сделать макет!» Ну, вы были бы правы, если бы это не означало, что вам придется менять каждое использование класса во всей кодовой базе, чтобы дать ему FooClass вместо BlahClass.
Не каждая кодовая база идеальна, и иногда вам просто нужно сделать что-то полезное. И это означает, что да, иногда вам нужно нарушить правило «только проверять публичные API».