PHPUnit:从抽象类构造函数中调用子方法

时间:2015-01-30 20:19:53

标签: phpunit

我在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

1 个答案:

答案 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');
}