phpunit-mockbuilder-设置模拟对象内部属性

phpunit-mockbuilder-设置模拟对象内部属性,php,phpunit,Php,Phpunit,是否可以使用禁用的构造函数和手动设置的受保护属性创建模拟对象 下面是一个愚蠢的例子: class A { protected $p; public function __construct(){ $this->p = 1; } public function blah(){ if ($this->p == 2) throw Exception(); } } class ATest ex

是否可以使用禁用的构造函数和手动设置的受保护属性创建模拟对象

下面是一个愚蠢的例子:

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来实现这一点?

您可以使用反射将属性公开,然后设置所需的值:

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

$reflection_property->setValue($a, 2);
无论如何,在您的示例中,不需要为引发的异常设置p值。您使用mock是为了能够控制对象行为,而不考虑其内部结构

因此,不是将p=2设置为引发异常,而是将模拟配置为在调用blah方法时引发异常:

$mockA = $this->getMockBuilder('A')
        ->disableOriginalConstructor()
        ->getMock();
$mockA->expects($this->any())
         ->method('blah')
         ->will($this->throwException(new Exception));
最后,奇怪的是,你竟然在考试中嘲笑A类。您通常模拟正在测试的对象所需的依赖关系


希望这有帮助。

我想留下一个方便的帮助方法,可以快速复制并粘贴到这里:

/**
 * 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);
}
当然,您可以在构造函数中使用模拟BlahClass,但是构造函数会将受保护的属性设置为您无法模拟的属性

所以你可能会想“重构构造函数,用FooClass代替BlahClass,这样你就不必在构造函数中实例化FooClass了,你可以用mock代替!”你是对的,如果这不意味着您必须更改整个代码库中该类的每个用法,以给它一个FooClass而不是BlahClass


并不是每个代码库都是完美的,有时候你只需要完成一些事情。这意味着,是的,有时您需要打破“仅测试公共API”规则。

基于上面的@rsahai91 answer,创建了一个新的帮助器,用于使多个方法可访问。可以是私有的,也可以是受保护的

/**
 * Makes any properties (private/protected etc) accessible on a given object via reflection
 *
 * @param $object - instance in which properties are being modified
 * @param array $properties - associative array ['propertyName' => 'propertyValue']
 * @return void
 * @throws ReflectionException
 */
public function setProperties($object, $properties)
{
    $reflection = new ReflectionClass($object);
    foreach ($properties as $name => $value) {
        $reflection_property = $reflection->getProperty($name);
        $reflection_property->setAccessible(true);
        $reflection_property->setValue($object, $value);
    }
}
示例用法:

$mock = $this->createMock(MyClass::class);

$this->setProperties($mock, [
    'propname1' => 'valueOfPrivateProp1',
    'propname2' => 'valueOfPrivateProp2'
]);

根据@gontrollez的公认答案,由于我们使用的是模拟构建器,因此无需调用
新的因为我们可以使用类名

    $a = $this->getMockBuilder(A::class)
        ->disableOriginalConstructor()
        ->getMock();

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

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

类A没有完全注入依赖项,我在它的构造函数中创建了几个类的新实例。。。所以我需要重写构造函数来模拟这些实例。这不是最好的方法,我想我会用依赖注入容器来代替它。你的代码肯定更容易测试。实现DI有几个选择,但这是一个非常简单的选择:不要在测试中使用依赖项注入容器!好的单元测试只测试一个类,所有依赖项都作为完全配置的模拟注入。如果你做不到这一点,那么你的体系结构就不好,应该加以改进,我不得不使用
$reflection\u property->setValue($a,2)
,因为第一种方法返回错误
致命错误:无法访问受保护的属性存储\u Api\u Version\u Resource\u Products::$\u字段…
仅用于记录-如果您正在测试非公共Api,那么您做得不对。单元测试是关于测试行为,而不是内部实现。->disableOriginalConstructor?