存根由类的构造函数调用的方法

2022-08-30 16:56:02

如何在 PHPUnit 中存根由被测类的构造函数调用的方法?例如,下面的简单代码将不起作用,因为当我声明 stubbed 方法时,已经创建了 stub 对象,并且我的方法被调用,未存根。

要测试的类:

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);
  }
}

答案 1

使用这样就不会调用构造函数。该名称有点误导,因为调用该方法最终会传递给 。这允许您在手动调用构造函数之前对返回的 mock 设置期望。disableOriginalConstructor()getMock()false$callOriginalConstructor

$stub = $this->getMockBuilder('ClassA')
             ->setMethods(array('getResultFromRemoteServer'))
             ->disableOriginalConstructor()
             ->getMock();
$stub->expects($this->any())
     ->method('getResultFromRemoteServer')
     ->will($this->returnValue('Poodle'));
$stub->__construct('dog52');
...

答案 2

问题不在于方法的存根,而在于你的类。

您正在构造函数中执行工作。为了将对象设置为状态,请获取一个远程文件。但该步骤不是必需的,因为对象不需要该数据处于有效状态。在实际调用 之前,您不需要该文件的结果。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());
  }
}

因此,您延迟加载了实际需要的远程访问。现在,您根本不需要存根,而是可以存根。您也不需要打开API进行测试并公开。getResultFromRemoteServergetFormattedgetResultFromRemoteServer

顺便说一句,即使它只是一个例子,我也会重写该类以阅读

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


另一种选择是扩展 Subject-Under-Test,并将该方法替换为您自己的返回 的实现。这意味着您不是在测试 实际的 ,而是 的子类,但无论如何,当您使用 Mock API 时,就会发生这种情况。getResultFromRemoteServerPoodleClassAClassA

从 PHP7 开始,你可以使用这样的匿名类:

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 之前,你只需编写一个常规类来扩展 Subject-Under-Test,并使用它来代替 Subject-Under-Test。或按本页其他位置所示使用。disableOriginalConstructor


推荐