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