我在PHPUnit中看到一个意想不到的(对我而言)行为,这是一个错误,还是我做错了什么? 简化测试用例:
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();
}
}
如果我在PHPUnit中运行上面的代码,我会收到以下错误:
Fatal error: Call to a member function speak() on a non-object in abstracttest.php on line 9
答案 0 :(得分:1)
将模拟通话更改为:
$mock = $this->getMockBuilder('speaker')
->setConstructorArgs([new SayHello()])
->setMethods([])
->getMock();
虽然这是测试的一个奇怪的例子。通常,我们不想创建我们正在测试的类的模拟。创建一个对象进入我们正在嘲笑的类是对我不利。
您调用了测试用例abstractTest
,但是您正在测试抽象类的具体子项。如果您打算测试abstractSpeaker
,那么您只需使用getMockForAbstractClass
:
$mock = $this->getMockBuilder('abstractSpeaker')
->setConstructorArgs([new SayHello()])
->setMethods([])
->getMockForAbstractClass();
然而,这引入了一个问题:setSpeaker
是一个在构造函数中调用的抽象方法,我们无法模拟它。就个人而言,我认为你在示例构造函数中做了太多,并将删除它。最终会是这样的:
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');
}
虽然在这种情况下,根本不需要使用setter和getter。您可以将扬声器传递给方法并直接调用speak
方法。
如果您打算测试具体的孩子,可以这样做:
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');
}