对类的构造函数调用的方法进行存根

时间:2011-04-05 03:17:03

标签: php phpunit

如何在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);
  }
}

2 个答案:

答案 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,如本页其他地方所示。