Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you use $this in callback to get protected properties of mocked class in phpunit?

Can you use $this inside callback to get protected properties of mocked class in phpunit? Or is there other way to achieve that?

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

This could be really useful if you think about injecting mocked objects. Sometimes class has hard-coded dependency for other class, but it creates it with method which you could theoretically mock and create mocked object instead of hard-coded object. Please look on the other example.

class A {
  protected $bar = "bar";

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

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

But lets say class B does something bad and I would like to replace it with mock.

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

Is this somehow achievable?

Of course without any surprise, currently, if I do this like above then I get error:

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

Using use keyword I can inherit $mockA from parent scope:

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

but this way I will try to access bar as public and I will get:

PHP Fatal error:  Cannot access protected property (...)
like image 531
Kamil Dziedzic Avatar asked Feb 09 '13 23:02

Kamil Dziedzic


2 Answers

As other answers have pointed out, $this can be used in Closures since PHP 5.4. A lesser known fact is that you can bind a closure to arbitrary objects and in fact make their private properties accessible like that. The method you need is bindTo(), which returns a new closure with different context.

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

Or more precise, your example would look like that:

 $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)));
like image 52
Fabian Schmengler Avatar answered Nov 02 '22 22:11

Fabian Schmengler


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);
    };
}
like image 34
qrazi Avatar answered Nov 03 '22 00:11

qrazi