2010-03-01 20 views
38

Busco la mejor manera de hacer las pruebas de la siguiente método estático (específicamente el uso de un modelo Doctrina):PHPUnit Mock objetos y métodos estáticos

class Model_User extends Doctrine_Record 
{ 
    public static function create($userData) 
    { 
     $newUser = new self(); 
     $newUser->fromArray($userData); 
     $newUser->save(); 
    } 
} 

Idealmente, me gustaría utilizar un objeto de burla para asegurar que "fromArray" (con los datos de usuario provistos) y "save" se llamaron, pero eso no es posible ya que el método es estático.

¿Alguna sugerencia?

Respuesta

42

Sebastian Bergmann, el autor de PHPUnit, recientemente publicó un blog sobre Stubbing and Mocking Static Methods. Con PHPUnit 3.5 y PHP 5.3, así como el uso constante de la ligadura dinámica estática, se puede hacer

$class::staticExpects($this->any()) 
     ->method('helper') 
     ->will($this->returnValue('bar')); 

Actualización:staticExpects es deprecated as of PHPUnit 3.8 y se eliminará por completo con versiones posteriores.

+11

A tener en cuenta " Este enfoque solo funciona para el tropezón y la burla de las llamadas a métodos estáticos donde la persona que llama y la llamada están en la misma clase. Esto se debe a que [los métodos estáticos son muerte a prueba] (http://misko.hevery.com/2008/12/15/static-methods-are-death-to-testability/). " –

+1

La función 'staticExpects' se ha eliminado a partir de PHPUnit v4. Ver [este hilo en github] (https://github.com/sebastianbergmann/phpunit-mock-objects/issues/137) para obtener una explicación de por qué. –

+4

Como sabemos que 'staticExpects' se ha eliminado de la versión reciente de PHPUnit, ¿cuál es la forma alternativa de lograr esto sin' staticExpects'? –

0

La prueba de métodos estáticos generalmente se considera como un poco difícil (como probablemente ya haya notado), especialmente antes de PHP 5.3.

¿No podría modificar su código para no usar un método estático? Realmente no veo por qué estás usando un método estático aquí, de hecho; esto probablemente podría volver a escribirse en algún código no estático, ¿no es así?


Por ejemplo, es posible que algo así no hacer el truco:

class Model_User extends Doctrine_Record 
{ 
    public function saveFromArray($userData) 
    { 
     $this->fromArray($userData); 
     $this->save(); 
    } 
} 

No estoy seguro que usted estará de pruebas; pero, al menos, hay un método estático ya ...

+0

Gracias por la sugerencia, es más estilo que nada. Podría hacer que el método no sea estático en esta instancia en particular (aunque preferiría poder usarlo sin crear instancias). –

+11

La pregunta es definitivamente sobre burlarse de los métodos estáticos: decirle al autor que "no use métodos estáticos" no corta la mostaza. – Lotus

10

En la actualidad existe la biblioteca AspectMock para ayudar con esto:

https://github.com/Codeception/AspectMock

$this->assertEquals('users', UserModel::tableName()); 
$userModel = test::double('UserModel', ['tableName' => 'my_users']); 
$this->assertEquals('my_users', UserModel::tableName()); 
$userModel->verifyInvoked('tableName'); 
+7

¡Esta biblioteca es dorada! Pero creo que deberían incluir una cláusula de exención de responsabilidad en su página: "Solo porque pueda probar las funciones globales y los métodos estáticos con nuestra biblioteca, esto no significa que deba escribir un nuevo código de esta manera". Leí en alguna parte que una mala prueba es mejor que no tener pruebas en absoluto, y con esta biblioteca puede agregar una red de seguridad a su código heredado. Solo asegúrate de escribir un nuevo código de una mejor manera :) – pedromanoel

0

Otro enfoque posible es con la biblioteca Moka:

$modelClass = Moka::mockClass('Model_User', [ 
    'fromArray' => null, 
    'save' => null 
]); 

$modelClass::create('DATA'); 
$this->assertEquals(['DATA'], $modelClass::$moka->report('fromArray')[0]); 
$this->assertEquals(1, sizeof($modelClass::$moka->report('save'))); 
1

Haría una nueva clase en el espacio de nombres de prueba de la unidad que amplía el Model_User y lo prueba. He aquí un ejemplo:

clase original:

class Model_User extends Doctrine_Record 
{ 
    public static function create($userData) 
    { 
     $newUser = new self(); 
     $newUser->fromArray($userData); 
     $newUser->save(); 
    } 
} 

Mock clase para llamar en prueba de la unidad (s):

use \Model_User 
class Mock_Model_User extends Model_User 
{ 
    /** \PHPUnit\Framework\TestCase */ 
    public static $test; 

    // This class inherits all the original classes functions. 
    // However, you can override the methods and use the $test property 
    // to perform some assertions. 
} 

En la prueba de unidad:

use Module_User; 
use PHPUnit\Framework\TestCase; 

class Model_UserTest extends TestCase 
{ 
    function testCanInitialize() 
    { 
     $userDataFixture = []; // Made an assumption user data would be an array. 
     $sut = new Mock_Model_User::create($userDataFixture); // calls the parent ::create method, so the real thing. 

     $sut::test = $this; // This is just here to show possibilities. 

     $this->assertInstanceOf(Model_User::class, $sut); 
    } 
} 
+0

Uso este método cuando no quiero incluir una biblioteca PHP adicional para hacerlo por mí. – b01

Cuestiones relacionadas