phpunit evita los argumentos del constructor para simular

85

¿Cuál es la forma de evitar que phpunit tenga que llamar al constructor para un objeto simulado? De lo contrario, necesitaría un objeto simulado como argumento de constructor, otro para eso, etc. La api parece ser así:

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

No consigo que funcione. Todavía se queja del argumento del constructor, incluso si se $callOriginalConstructorestablece en falso.

Solo tengo un objeto en el constructor y es una inyección de dependencia. Entonces no creo que tenga un problema de diseño allí.

yhw42
fuente

Respuestas:

139

Puede usar en getMockBuilderlugar de solo getMock:

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

Consulte la sección sobre "Pruebas dobles" en la documentación de PHPUnit para obtener más detalles.

Aunque puede hacer esto, es mucho mejor que no sea necesario. Puede refactorizar su código para que en lugar de una clase concreta (con un constructor) que necesite inyectarse, solo dependa de una interfaz. Esto significa que puede simular o eliminar la interfaz sin tener que decirle a PHPUnit que modifique el comportamiento del constructor.

dave1010
fuente
Esto funciona muy bien para mí. Sin embargo, debería ser el ejemplo 10.3. Traté de editar la publicación, pero SO la eché hacia atrás por ser una edición demasiado corta.
Mateo
Gracias @Lytithwyn. Actualización de la respuesta.
dave1010
42

Aqui tienes:

    // 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);
Matthew Purdon
fuente
Esto parece ser casi lo que quiero. Quiero llamar a getMock con solo la clase para ser burlada y $ callMockConstructor. ¿Cómo? algo como esto: $ this-> getMock ($ classToMock, $ callMockConstructor). Lo único que se me ocurre es ir a la fuente de PHPUnit y cambiarlo a default = false.
Gutzofter
1
Cambié el valor predeterminado a falso en testcase.php. Pensaría que estaría configurado como falso de forma predeterminada. Burlarse de un constructor parece muy extraño
Gutzofter
Excelente respuesta. Justo lo que estaba buscando
Hades
4

Como apéndice, quería adjuntar expects()llamadas a mi objeto simulado y luego llamar al constructor. En PHPUnit 3.7.14, el objeto que se devuelve cuando llama disableOriginalConstructor()es literalmente un objeto.

// 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)

Desafortunadamente, en PHP 5.4 hay una nueva opción que no están usando:

ReflectionClass :: newInstanceWithoutConstructor

Como esto no estaba disponible, tuve que reflejar manualmente la clase y luego invocar el constructor.

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

Tenga en cuenta que, si functionCallFromConstructes así protected, debe usarlo específicamente setMethods()para que se burle del método protegido. Ejemplo:

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

setMethods()debe ser llamado antes de la expect()llamada. Personalmente, encadeno esto después disableOriginalConstructor()pero antes getMock().

Steve Tauber
fuente
No tengo idea si esto es un olor a código, pero funcionó muy bien para mí y solo quería agradecerles.
Devbanana
1

Quizás necesite crear un código auxiliar para pasarlo como argumento del constructor. Entonces puedes romper esa cadena de objetos simulados.

Glenn Moss
fuente
1

Alternativamente, puede agregar un parámetro a getMock para evitar la llamada del constructor predeterminado.

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

Aún así, creo que la respuesta de dave1010 se ve mejor, esto es solo para completar.

Hans Wouters
fuente
1

Esta pregunta es un poco vieja, pero para los nuevos visitantes, puede hacerlo usando el createMockmétodo (previamente llamado createTestDoublee introducido en v5.4.0).

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

Como puede ver en el código a continuación extraído de la PHPUnit\Framework\TestCaseclase (en phpunit/src/framework/TestCase.php), básicamente creará un objeto simulado sin llamar al constructor original .

/** PHPUnit\Framework\TestCase::createMock method */
protected function createMock(string $originalClassName): MockObject
{
    return $this->getMockBuilder($originalClassName)
                ->disableOriginalConstructor()
                ->disableOriginalClone()
                ->disableArgumentCloning()
                ->disallowMockingUnknownTypes()
                ->getMock();
}
Wesley Gonçalves
fuente
0

PHPUnit está diseñado para llamar al constructor en objetos simulados; para evitar esto, debe:

  1. Inyecte un objeto simulado como dependencia en el objeto del que tiene problemas para burlarse
  2. Cree una clase de prueba que amplíe la clase que está intentando llamar y que no llama al constructor padre
silfreed
fuente