Подтвердить что ты не робот

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

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

4b9b3361

Ответ 1

Вы можете сделать свойство общедоступным с помощью 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. Обычно вы издеваетесь над зависимостями, которые требуется тестировать.

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

Ответ 2

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

/**
 * 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);
}

Ответ 3

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

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

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

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

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