Stubbing метод, вызываемый конструктором класса

Как можно заглушить метод в 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); } } 

Solutions Collecting From Web of "Stubbing метод, вызываемый конструктором класса"

Проблема заключается не в том, чтобы окупить метод, а в ваш класс.

Вы выполняете работу в конструкторе. Чтобы установить объект в состояние, вы извлекаете удаленный файл. Но этот шаг не требуется, поскольку объект не нуждается в том, чтобы данные находились в правильном состоянии. Вам не нужен результат из файла, прежде чем вы назовете 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 чтобы на самом деле иметь findById public. Просто держите его в безопасности, потому что это то, что у вас было в вашем примере.


Другой вариант заключается в расширении Subject-Under-Test и замене метода getResultFromRemoteServer на вашу собственную реализацию, возвращающую Poodle . Это означает, что вы не тестируете фактический ClassA , а подкласс ClassA , но это то, что происходит, когда вы используете Mock API в любом случае.

Начиная с 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 . Или используйте disableOriginalConstructor как показано в другом месте на этой странице.

Используйте 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'); ...