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

jon*_*tyc 22 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);
  }
}
Run Code Online (Sandbox Code Playgroud)

测试代码:

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

Dav*_*ess 48

使用,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');
...
Run Code Online (Sandbox Code Playgroud)


Gor*_*don 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());
  }
}
Run Code Online (Sandbox Code Playgroud)

所以你懒得加载远程访问它实际需要的时候.现在你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));
    }
}
Run Code Online (Sandbox Code Playgroud)

由于它是一个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);
}
Run Code Online (Sandbox Code Playgroud)

在PHP7之前,您只需编写一个扩展测试对象的常规类,并使用它而不是测试对象.或使用disableOriginalConstructor本页其他地方所示.