Reputation: 23
I'm seeing an unexpected (to me) behaviour with PHPUnit, is this a bug, or am I doing something wrong? Simplified test case:
abstract class abstractSpeaker {
public function __construct($param) {
$this->setSpeaker($param);
$this->getSpeaker()->speak(); //bad line, causes error
$this->tellSpeakerToSpeak(); //this lines ok
}
abstract function setSpeaker($value);
abstract function getSpeaker();
function tellSpeakerToSpeak() {
$this->getSpeaker()->speak(); //this line works, but is same as bad line
}
}
class speaker extends abstractSpeaker {
protected $speaker;
function setSpeaker($value) {
$this->speaker = $value;
}
function getSpeaker() {
return $this->speaker;
}
}
class SayHello {
public function speak() { print "hello"; }
}
class abstractTest extends \PHPUnit_Framework_TestCase {
public function testIndex() {
$mock = $this->getMockBuilder(speaker::class)
->setConstructorArgs([new SayHello()])
->setMethods([])
->getMock();
$mock->tellSpeakerToSpeak();
}
}
If I run the above code in PHPUnit, I get the following error:
Fatal error: Call to a member function speak() on a non-object in abstracttest.php on line 9
Upvotes: 0
Views: 591
Reputation: 43800
Change your mock call to:
$mock = $this->getMockBuilder('speaker')
->setConstructorArgs([new SayHello()])
->setMethods([])
->getMock();
Though this is an odd example for a test. Typically, we don't want to create a mock of the class that we are testing. And creating an object to pass into the class that we are mocking is kind of backwards to me.
You called your test case abstractTest
but you are testing the concrete child of the abstract class. If your intention is to test abstractSpeaker
then you would just use getMockForAbstractClass
like so:
$mock = $this->getMockBuilder('abstractSpeaker')
->setConstructorArgs([new SayHello()])
->setMethods([])
->getMockForAbstractClass();
However this introduces the problem that setSpeaker
is an abstract method that you are calling in your constructor and we are not able to mock that. Personally, I think that you are doing too much in the example constructor and would remove it. Which would end up like this:
public function testIndex() {
$mock = $this->getMockBuilder('abstractSpeaker')
->setMethods(['setSpeaker', 'getSpeaker'])
->getMockForAbstractClass();
$mockSpeaker = $this->getMockBuilder('SayHello')
->setMethods(['speak'])
->getMock();
$mockSpeaker->expects($this->once())
->method('speak')
->will($this->returnCallback(function() { print 'Hello' }));
$mock->expects($this->once())
->method('setSpeaker')
->with($mockSpeaker);
$mock->expects($this->once())
->method('getSpeaker')
->willReturn($mockSpeaker);
$mock->tellSpeakerToSpeak($mockSpeaker);
$this->expectOutputString('Hello');
}
Though in this case, there really is no need to use the setters and getters at all. And you could just pass the speaker to the method and call the speak
method on it directly.
If you were intending to test the concrete child, you could do it like this:
public function testIndex() {
$mock = $this->getMockBuilder('SayHello')
->setMethods(['speak'])
->getMock();
$mock->expects($this->once())
->method('speak')
->will($this->returnCallback(function() { print 'Hello' }));
$sut = new speaker($mock);
$this->expectOutputString('Hello');
}
Upvotes: 1