如何在PHPUnit中存根一个由test的构造函数中的类调用的方法?例如下面的简单代码将不起作用,因为当我声明存根方法时,存根对象已经被创建并且我的方法被调用,未被取消。
要测试的课程:
class ClassA {
private $dog;
private $formatted;
public function __construct($param1) {
$this->dog = $param1;
$this->getResultFromRemoteServer();
}
// Would normally be private, made public for stubbing
public getResultFromRemoteServer() {
$this->formatted = file_get_contents('http://whatever.com/index.php?'.$this->dog);
}
public getFormatted() {
return ("The dog is a ".$this->formatted);
}
}
测试代码:
class ClassATest extends PHPUnit_Framework_TestCase {
public function testPoodle() {
$stub = $this->getMockBuilder('ClassA')
->setMethods(array('getResultFromRemoteServer'))
->setConstructorArgs(array('dog52'))
->getMock();
$stub->expects($this->any())
->method('getResultFromRemoteServer')
->will($this->returnValue('Poodle'));
$expected = 'This dog is a Poodle';
$actual = $stub->getFormatted();
$this->assertEquals($expected, $actual);
}
}
答案 0 :(得分:47)
使用disableOriginalConstructor()
以便getMock()
不会调用构造函数。该名称有点误导,因为调用该方法最终会传递false
$callOriginalConstructor
。这允许您在手动调用构造函数之前设置对返回的mock的期望。
$stub = $this->getMockBuilder('ClassA')
->setMethods(array('getResultFromRemoteServer'))
->disableOriginalConstructor()
->getMock();
$stub->expects($this->any())
->method('getResultFromRemoteServer')
->will($this->returnValue('Poodle'));
$stub->__construct('dog52');
...
答案 1 :(得分:14)
问题不在于方法的存根,而在于你的课程。
您正在构造函数中工作。要将对象设置为状态,请获取远程文件。但该步骤不是必需的,因为该对象不需要该数据处于有效状态。在实际调用getFormatted
之前,您不需要文件中的结果。
你可以推迟加载:
class ClassA {
private $dog;
private $formatted;
public function __construct($param1) {
$this->dog = $param1;
}
protected getResultFromRemoteServer() {
if (!$this->formatted) {
$this->formatted = file_get_contents(
'http://whatever.com/index.php?' . $this->dog
);
}
return $this->formatted;
}
public getFormatted() {
return ("The dog is a " . $this->getResultFromRemoteServer());
}
}
所以你懒得加载远程访问它实际需要的时候。现在你根本不需要存根getResultFromRemoteServer
,而是可以存根getFormatted
。您也无需打开API进行测试,然后公开getResultFromRemoteServer
。
在旁注中,即使只是一个例子,我也会重写那个类来阅读
class DogFinder
{
protected $lookupUri;
protected $cache = array();
public function __construct($lookupUri)
{
$this->lookupUri = $lookupUri;
}
protected function findById($dog)
{
if (!isset($this->cache[$dog])) {
$this->cache[$dog] = file_get_contents(
urlencode($this->lookupUri . $dog)
);
}
return $this->cache[$id];
}
public function getFormatted($dog, $format = 'This is a %s')
{
return sprintf($format, $this->findById($dog));
}
}
由于它是一个Finder,现在实际公开findById
可能更有意义。只是保护它,因为这就是你的例子。
另一个选项是扩展主题测试并将方法getResultFromRemoteServer
替换为您自己的实现,返回Poodle
。这意味着您没有测试实际的ClassA
,而是测试ClassA
的子类,但这是当您使用Mock API时会发生的情况。
从PHP7开始,您可以使用这样的Anonymous类:
public function testPoodle() {
$stub = new class('dog52') extends ClassA {
public function getResultFromRemoteServer() {
return 'Poodle';
}
};
$expected = 'This dog is a Poodle';
$actual = $stub->getFormatted();
$this->assertEquals($expected, $actual);
}
在PHP7之前,您只需编写一个扩展测试对象的常规类,并使用它而不是测试对象。或者使用disableOriginalConstructor
,如本页其他地方所示。