Заглушка метода, вызываемого конструктором класса - PullRequest
22 голосов
/ 05 апреля 2011

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

Ответы [ 2 ]

48 голосов
/ 05 апреля 2011

Используйте 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');
...
14 голосов
/ 05 апреля 2011

Проблема не в заглушке метода, а в вашем классе.

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

В sidenote, даже если это всего лишь пример, я переписал бы этот класс, чтобы прочитать

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 сейчас. Просто держите его защищенным, потому что это то, что вы имели в своем примере


Другим вариантом будет расширение Подопытного и замена метода 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 и использовали его вместо Subject-Under-Test . Или используйте disableOriginalConstructor, как показано в другом месте на этой странице.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...