存根由类的构造函数调用的方法 [英] Stubbing a method called by a class' constructor

查看:101
本文介绍了存根由类的构造函数调用的方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

How does one stub a method in PHPUnit that is called by the class under test's constructor? The simple code below for example won't work because by the time I declare the stubbed method, the stub object has already been created and my method called, unstubbed.

要测试的课程:

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

推荐答案

问题不是方法的存根,而是您的类.

The problem is not the stubbing of the method, but your class.

您正在构造器中进行工作.为了将对象设置为状态,您需要获取一个远程文件.但是该步骤不是必需的,因为对象不需要该数据处于有效状态.在实际调用getFormatted之前,不需要文件中的结果.

You are doing work in the constructor. In order to set the object into state, you fetch a remote file. But that step is not necessary, because the object doesn't need that data to be in a valid state. You dont need the result from the file before you actually call 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公开.

so you are lazy loading the remote access to when it's actually needed. Now you dont need to stub getResultFromRemoteServer at all, but can stub getFormatted instead. You also won't need to open your API for the testing and make getResultFromRemoteServer public then.

在旁注中,即使只是一个例子,我也会重写该类以供阅读

On a sidenote, even if it's just an example, I would rewrite that class to read

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可能更有意义.只需对其进行保护,因为这就是您的示例所具有的内容.

Since it's a Finder, it might make more sense to actually have findById public now. Just keeping it protected because that's what you had in your example.

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

The other option would be to extend the Subject-Under-Test and replace the method getResultFromRemoteServer with your own implementation returning Poodle. This would mean you are not testing the actual ClassA, but a subclass of ClassA, but this is what happens when you use the Mock API anyway.

从PHP7开始,您可以利用这样的Anonymous类:

As of PHP7, you could utilize an Anonymous class like this:

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,如本页其他位置所示.

Before PHP7, you'd just write a regular class extending the Subject-Under-Test and use that instead of the Subject-Under-Test. Or use disableOriginalConstructor as shown elsewhere on this page.

这篇关于存根由类的构造函数调用的方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆