phpunit избегает аргументов конструктора для имитации

85

Как избежать вызова phpunit конструктора для фиктивного объекта? В противном случае мне понадобится фиктивный объект в качестве аргумента конструктора, другой для этого и т. Д. Кажется, что api выглядит так:

getMock($className, $methods = array(), array $arguments = array(),
        $mockClassName = '', $callOriginalConstructor = TRUE,
        $callOriginalClone = TRUE, $callAutoload = TRUE)

Я не могу заставить его работать. Он по-прежнему жалуется на аргумент конструктора, даже если для него $callOriginalConstructorустановлено значение false.

У меня только один объект в конструкторе, и это инъекция зависимости. Так что я не думаю, что у меня здесь проблемы с дизайном.

yhw42
источник

Ответы:

139

Вы можете использовать getMockBuilderвместо просто getMock:

$mock = $this->getMockBuilder('class_name')
    ->disableOriginalConstructor()
    ->getMock();

См. Подробности в разделе «Тестовые двойники» в документации PHPUnit .

Хотя вы можете это сделать, гораздо лучше не делать этого. Вы можете провести рефакторинг своего кода, чтобы вместо конкретного класса (с конструктором), который нужно было внедрить, вы зависели только от интерфейса. Это означает, что вы можете имитировать интерфейс или заглушить его, не указывая PHPUnit на изменение поведения конструктора.

dave1010
источник
Это отлично работает для меня. Хотя это должен быть пример 10.3. Я попытался отредактировать сообщение, но ТАК отклонил его за то, что он был слишком коротким.
Мэтью
Спасибо @Lytithwyn. Обновил ответ.
dave1010
42

Ну вот:

    // Get a Mock Soap Client object to work with.
    $classToMock = 'SoapClient';
    $methodsToMock = array('__getFunctions');
    $mockConstructorParams = array('fake wsdl url', array());
    $mockClassName = 'MyMockSoapClient';
    $callMockConstructor = false;
    $mockSoapClient = $this->getMock($classToMock,
                                     $methodsToMock,
                                     $mockConstructorParams,
                                     $mockClassName,
                                     $callMockConstructor);
Мэтью Пёрдон
источник
Кажется, это почти то, что я хочу. Я хочу вызвать getMock только с классом, который нужно издеваться, и с $ callMockConstructor. Как? примерно так: $ this-> getMock ($ classToMock, $ callMockConstructor). Единственное, что я мог придумать, - это зайти в источник PHPUnit и изменить его на default = false.
Gutzofter
1
Я изменил значение по умолчанию в testcase.php на false. Можно подумать, что по умолчанию установлено значение false. Издевательство над конструктором кажется очень странным
Gutzofter
Отличный ответ. Именно то, что я искал
Hades
4

В качестве дополнения я хотел прикрепить expects()вызовы к своему фиктивному объекту, а затем вызвать конструктор. В PHPUnit 3.7.14 объект, который возвращается при вызове disableOriginalConstructor(), буквально является объектом.

// Use a trick to create a new object of a class
// without invoking its constructor.
$object = unserialize(
sprintf('O:%d:"%s":0:{}', strlen($className), $className)

К сожалению, в PHP 5.4 есть новая опция, которую они не используют:

ReflectionClass :: newInstanceWithoutConstructor

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

$mock = $this->getMockBuilder('class_name')
    ->disableOriginalConstructor()
    ->getMock();

$mock->expect($this->once())
    ->method('functionCallFromConstructor')
    ->with($this->equalTo('someValue'));

$reflectedClass = new ReflectionClass('class_name');
$constructor = $reflectedClass->getConstructor();
$constructor->invoke($mock);

Обратите внимание: если functionCallFromConstructэто так protected, вы должны использовать специально для setMethods()имитации защищенного метода. Пример:

    $mock->setMethods(array('functionCallFromConstructor'));

setMethods()должен быть вызван до expect()звонка. Лично я связываю это после, disableOriginalConstructor()но до getMock().

Стив Таубер
источник
Понятия не имею, запах ли это кода, но у меня это отлично сработало, и я просто хотел вас поблагодарить.
devbanana
1

Возможно, вам нужно создать заглушку для передачи в качестве аргумента конструктора. Затем вы можете разорвать эту цепочку фиктивных объектов.

Гленн Мосс
источник
1

В качестве альтернативы вы можете добавить параметр в getMock, чтобы предотвратить вызов конструктора по умолчанию.

$mock = $this->getMock(class_name, methods = array(), args = array(), 
        mockClassName = '', callOriginalConstructor = FALSE);

Тем не менее, я думаю, что ответ dave1010 выглядит лучше, это просто для полноты картины.

Ханс Воутерс
источник
1

Этот вопрос немного устарел, но для новых посетителей вы можете сделать это с помощью createMockметода (ранее createTestDoubleвызывавшегося и представленного в v5.4.0).

$mock = $this->createMock($className);

Как вы можете видеть в приведенном ниже коде, извлеченном из PHPUnit\Framework\TestCaseкласса (in phpunit/src/framework/TestCase.php), он в основном создает фиктивный объект без вызова исходного конструктора .

/** PHPUnit\Framework\TestCase::createMock method */
protected function createMock(string $originalClassName): MockObject
{
    return $this->getMockBuilder($originalClassName)
                ->disableOriginalConstructor()
                ->disableOriginalClone()
                ->disableArgumentCloning()
                ->disallowMockingUnknownTypes()
                ->getMock();
}
Уэсли Гонсалвеш
источник
0

PHPUnit предназначен для вызова конструктора имитируемых объектов; чтобы предотвратить это, вам следует:

  1. Внедрить фиктивный объект как зависимость в объект, с которым у вас возникли проблемы с издевательством
  2. Создайте тестовый класс, который расширяет класс, который вы пытаетесь вызвать, но не вызывает родительский конструктор.
Silfreed
источник