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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18558183/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 17:47:40  来源:igfitidea点击:

phpunit - mockbuilder - set mock object internal property

phpphpunit

提问by inf3rno

Is it possible to create a mock object with disabled constructor and manually setted protected properties?

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

Here is an idiotic example:

这是一个愚蠢的例子:

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

So I wanna inject the p value which is protected, so I can't. Should I define setter or IoC, or I can do this with phpunit?

所以我想注入受保护的 p 值,所以我不能。我应该定义 setter 还是 IoC,或者我可以用 phpunit 来做到这一点?

回答by gontrollez

You can make the property public by using Reflection, and then set the desired value:

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

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

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

Anyway in your example you don't need to set p value for the Exception to be raised. You are using a mock for being able to take control over the object behaviour, without taking into account it's internals.

无论如何,在您的示例中,您不需要为要引发的异常设置 p 值。您正在使用模拟来控制对象行为,而不考虑它的内部结构。

So, instead of setting p = 2 so an Exception is raised, you configure the mock to raise an Exception when the blah method is called:

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

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

Last, it's strange that you're mocking the A class in the ATest. You usually mock the dependencies needed by the object you're testing.

最后,奇怪的是你在 ATest 中模拟 A 类。您通常会模拟您正在测试的对象所需的依赖项。

Hope this helps.

希望这可以帮助。

回答by rsahai91

Thought i'd leave a handy helper method that could be quickly copy and pasted here:

以为我会留下一个方便的辅助方法,可以在这里快速复制和粘贴:

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

回答by Zachary Burnham

It would be amazing if every codebase used DI and IoC, and never did stuff like this:

如果每个代码库都使用 DI 和 IoC,并且从未做过这样的事情,那就太棒了:

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

You can use a mock BlahClass in the constructor, sure, but then the constructor sets a protected property to something you CAN'T mock.

当然,您可以在构造函数中使用模拟 BlahClass,但随后构造函数会将受保护的属性设置为您无法模拟的内容。

So you're probably thinking "Well refactor the constructor to take a FooClass instead of a BlahClass, then you don't have to instantiate the FooClass in the constructor, and you can put in a mock instead!" Well, you'd be right, if that didn't mean you would have to change every usage of the class in the entire codebase to give it a FooClass instead of a BlahClass.

所以你可能在想“好吧,重构构造函数以采用 FooClass 而不是 BlahClass,那么你不必在构造函数中实例化 FooClass,你可以放入一个模拟!” 好吧,您是对的,如果这并不意味着您必须更改整个代码库中类的每个用法,以给它一个 FooClass 而不是 BlahClass。

Not every codebase is perfect, and sometimes you just need to get stuff done. And that means, yes, sometimes you need to break the "only test public APIs" rule.

并非每个代码库都是完美的,有时您只需要完成工作即可。这意味着,是的,有时您需要打破“仅测试公共 API”规则。

回答by Oli Girling

Based on @rsahai91 answer above, created a new helper for making multiple methods accessible. Can be private or protected

基于上面的@rsahai91 回答,创建了一个新的帮助程序,用于使多种方法可访问。可以是私有的或受保护的

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

Example use:

使用示例:

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

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

回答by Freefri

Based on the accepted answer from @gontrollez, since we are using a mock builder we do not have the need to call new A;since we can use the class name instead.

根据@gontrollez 接受的答案,由于我们使用的是模拟构建器,因此我们不需要调用,new A;因为我们可以使用类名。

    $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);