2010-03-01 9 views
38

Cerco il modo migliore per andare a testare il seguente metodo statico (in particolare utilizzando un modello Doctrine):PHPUnit Mock oggetti e metodi statici

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

Idealmente, vorrei utilizzare un oggetto fittizio per assicurare che "fromArray" (con i dati utente forniti) e "save" sono stati chiamati, ma ciò non è possibile in quanto il metodo è statico.

Qualche suggerimento?

risposta

42

Sebastian Bergmann, autore di PHPUnit, ha recentemente pubblicato un post sul blog Stubbing and Mocking Static Methods. Con PHPUnit 3.5 e PHP 5.3, così come l'uso costante di ritardo binding statico, si può fare

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

Aggiornamento:staticExpects è deprecated as of PHPUnit 3.8 e verranno rimossi completamente con le versioni successive.

+11

Degne di nota " Questo approccio funziona solo per lo stub e il mocking di chiamate a metodi statici in cui caller e callee si trovano nella stessa classe. Questo perché [metodi statici sono death to testability] (http://misko.hevery.com/2008/12/15/static-methods-are-death-to-estibility/). " –

+1

La funzione 'staticExpects' è stata rimossa da PHPUnit v4. Vedi [questo thread su github] (https://github.com/sebastianbergmann/phpunit-mock-objects/issues/137) per una spiegazione del perché. –

+4

Come sappiamo che 'staticExpects' è stato rimosso dalla versione recente di PHPUnit, qual è il modo alternativo per ottenere ciò senza' staticExpects'? –

0

Il test dei metodi statici è generalmente considerato un po 'difficile (come probabilmente già notato), in particolare prima di PHP 5.3.

Non si può modificare il codice per non utilizzare un metodo statico? Non vedo davvero perché stai usando un metodo statico qui, in effetti; questo potrebbe probabilmente essere riscritto su un codice non statico, non è così?


Per esempio, potrebbe qualcosa come questo non fare il trucco:

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

Non sono sicuro che sarete in prova; ma, almeno, nessun metodo statico più ...

+0

Grazie per il suggerimento, è più lo stile che altro. Potrei rendere il metodo non statico in questa particolare istanza (anche se preferirei poterlo usare senza istanziare). –

+11

La domanda riguarda sicuramente il modo di deridere i metodi statici: dire all'autore di "non usare metodi statici" non taglia la senape. – Lotus

10

V'è ora la libreria AspectMock per aiutare con questo:

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

Questa libreria è d'oro! Ma penso che dovrebbero mettere un disclaimer sulla loro pagina: "Solo perché puoi testare funzioni globali e metodi statici con la nostra libreria, questo non significa che dovresti scrivere un nuovo codice in questo modo." Ho letto da qualche parte che un test negativo è meglio che non avere test, e con questa libreria puoi aggiungere una rete di sicurezza al tuo codice legacy. Assicurati di scrivere il nuovo codice in un modo migliore :) – pedromanoel

0

Un altro approccio possibile è con la libreria 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

Vorrei creare una nuova classe nello spazio dei nomi dell'unità di test che estende Model_User e testarlo. Ecco un esempio:

classe originale:

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

Mock classe per chiamare a test di unità (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. 
} 

Nel vostro unit test:

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

Io uso questo metodo quando non voglio includere una libreria PHP extra per farlo per me. – b01