7

您可以在回调中使用 $this 来获取 phpunit 中模拟类的受保护属性吗?或者还有其他方法可以实现吗?

 $mock = $this->getMock('A', array('foo'));
 $mock->expects($this->any())->method('foo')->will(
     $this->returnCallback(function() {
         return $this->bar;
 }));

如果您考虑注入模拟对象,这可能非常有用。有时类对其他类具有硬编码的依赖关系,但它使用您理论上可以模拟并创建模拟对象而不是硬编码对象的方法创建它。请看另一个例子。

class A {
  protected $bar = "bar";

  public function foo () {
    $b = new B();
    return $b->fizz($this->bar);
  }
}

class B {
  public function fizz ($buzz) {
    return $buzz;
  }
}

但是让我们说 B 类做的不好,我想用模拟来代替它。

 $mockB = $this->getMock('B');
 // (...) - and probably mock other things
 $mockA = $this->getMock('A', array('foo'));
 $mockA->expects($this->any())->method('foo')->will(
     $this->returnCallback(function() use ($mockB) {
         return $mockB->fizz($this->bar);
 }));

这在某种程度上可以实现吗?

当然,毫无疑问,目前,如果我像上面那样做,那么我会得到错误:

PHP Fatal error:  Using $this when not in object context in (...)

使用use关键字我可以从父范围继承 $mockA :

 $mockB = $this->getMock('B');
 // (...) - and probably mock other things
 $mockA = $this->getMock('A', array('foo'));
 $mockA->expects($this->any())->method('foo')->will(
     $this->returnCallback(function() use ($mockA, $mockB) {
         return $mockB->fizz($mockA->bar);
 }));

但这样我会尝试以公共方式访问 bar ,我会得到:

PHP Fatal error:  Cannot access protected property (...)
4

4 回答 4

7

正如其他答案所指出的那样,$this可以在 PHP 5.4 以来的闭包中使用。一个鲜为人知的事实是,您可以将闭包绑定到任意对象,并且实际上可以像这样访问它们的私有属性。您需要的方法是bindTo(),它返回一个具有不同上下文的新闭包。

$cb = function() {
  return $this->bar;
};
$cb = $cb->bindTo($mockA);

或更准确地说,您的示例如下所示:

 $mockB = $this->getMock('B');
 // (...) - and probably mock other things
 $mockA = $this->getMock('A', array('foo'));
 $fooCallback = function() use (&$mockB) {
     return $mockB->fizz($this->bar);
 };
 $mockA->expects($this->any())->method('foo')->will(
     $this->returnCallback($fooCallback->bindTo($mockA)));
于 2013-02-10T22:15:57.067 回答
1

As pointed out by dev-null-dweller, in PHP 5.4 you can use $this within the closure as if you where working normally in the method.

In 5.3 you can mimic this behavior by doing:

public function getCallback(B $b) {
    $self = $this;
    return function() use($b, $self) { 
        return $b->fizz($self->bar);
    };
}
于 2013-02-10T19:45:21.053 回答
0

从 php 5.4 开始,您可以$this在闭包中使用,但您必须从包含以下受保护属性的对象返回此回调:

class A {
    protected $bar = "bar";

    public function foo () {
        $b = new B();
        return $b->fizz($this->bar);
    }

    public function getCallback(B $b) {
        return function() use($b) { 
            return $b->fizz($this->bar);
        };
    }
}

class B {
    public function fizz ($buzz) {
        return $buzz;
    }
}

$mockA = new A;
$mockB = new B;

$callBack = $mockA->getCallback($mockB);
var_dump($callBack() === $mockA->foo());

但是,如果您需要获取受保护属性的值,则应为其定义公共 getter。这样,测试也将在 php 5.3 中工作

于 2013-02-10T11:13:37.733 回答
0

对于 PHP >= 5.4,使用Closure::bind()

 $mockB = $this->getMock('B');
 // (...) - and probably mock other things
 $mockA = $this->getMock('A', array('foo'));
 $mockA->expects($this->any())->method('foo')->will(
     $this->returnCallback(
         \Closure::bind(function() use ($mockB) {
             return $mockB->fizz($this->bar);
 }, $mockA, 'A')));

对于 PHP >= 5.3,使用反射

 $mockB = $this->getMock('B');
 // (...) - and probably mock other things
 $mockA = $this->getMock('A', array('foo'));
 $mockA->expects($this->any())->method('foo')->will(
     $this->returnCallback(function() use ($mockA, $mockB) {
         $barProp = new \ReflectionProperty('A', 'bar');
         $barProp->setAccessible(true);
         return $mockB->fizz($barProp->getValue($mockA));
 }));
于 2018-09-12T16:45:50.323 回答