2013-08-16 3 views
20

Sto provando a creare un test unitario piuttosto standard dove chiamo un metodo e asserisco la sua risposta, tuttavia il metodo che sto test chiama un altro metodo all'interno della stessa classe che fa un po 'di sollevamento pesante.metodo di test phpunit che chiama altri metodi di classe che necessitano di simulazione

Voglio prendere in giro questo metodo, ma eseguo ancora il metodo che sto testando così com'è, solo con il valore fittizio restituito dalla chiamata all'altro metodo.

Ho semplificato l'esempio per renderlo il più semplice possibile.

class MyClass 
{ 

    // I want to test this method, but mock the handleValue method to always return a set value. 

    public function testMethod($arg) 
    { 

     $value = $arg->getValue(); 

     $this->handleValue($value); 

    } 


    // This method needs to be mocked to always return a set value. 

    public function handleValue($value) 
    { 

     // Do a bunch of stuff... 
     $value += 20; 

     return $value; 

    } 

} 

Il mio tentativo di scrivere i test.

class MyClassTest extends \PHPUnit_Framework_TestCase 
{ 


    public function testTheTestMethod() 
    { 

     // mock the object that is passed in as an arg 
     $arg = $this->getMockBuilder('SomeEntity')->getMock(); 
     $arg->expects($this->any()) 
      ->method('getValue') 
      ->will($this->returnValue(10)); 

     // test handle document() 
     $myClass = new MyClass(); 

     $result = $myClass->testMethod($arg); 

     // assert result is the correct 
     $this->assertEquals($result, 50); 

    } 

} 

ho cercato beffardo l'oggetto MyClass, ma quando lo faccio e chiamo la testMethod esso restituisce sempre null. Ho bisogno di un modo per deridere l'unico metodo, ma lasciare il resto dell'oggetto intatto.

risposta

21

È possibile prendere in giro la classe che si sta testando e specificare il metodo che si desidera prendere in giro.

$mock = $this->getMockBuilder('MyClass') 
    ->setMethods(array('handleValue')) 
    ->getMock(); 

$mock->expects($this->once()) 
    ->method('handleValue') 
    ->will($this->returnValue(23)) //Whatever value you want to return 

Tuttavia, IMO questo non è la migliore idea per le vostre prove. Test come questo renderanno molto più difficile il refactoring. Stai specificando l'implementazione della classe piuttosto che il comportamento che la classe dovrebbe avere. Se handleValue sta facendo un lavoro molto complicato che rende difficile il test, considera di spostare la logica in una classe separata e di iniettarla nella tua classe. Quindi puoi creare una simulazione di quella classe e passarla a testMethod. In questo modo avrai il vantaggio di rendere MyClass più estendibile se è necessario adattare il suo comportamento a handleValue.

http://www.oodesign.com/strategy-pattern.html

Come regola generale, non si dovrebbe prendere in giro il sistema che si sta testando.

+0

Buona risposta, grazie. – greg

+1

cosa fare se il metodo 'handleValue' è in un'altra classe? – Somar

+0

Buona risposta, di solito applico le dipendenze di test quando devo usare parziali mock tra il mio metodo di test ei metodi mocked (se è impossibile per la logica refactoring) – kurroman

9

È possibile specificare quali metodi per deridere (finto parziale) con setMethods():

// Let's do a `partial mock` of the object. By passing in an array of methods to `setMethods` 
// we are telling PHPUnit to only mock the methods we specify, in this case `handleValue()`. 

$csc = $this->getMockBuilder('Lightmaker\CloudSearchBundle\Controller\CloudSearchController') 
      ->setConstructorArgs($constructor) 
      ->setMethods(array('handleValue')) 
      ->getMock(); 

// Tell the `handleValue` method to return 'bla' 
$csc->expects($this->any()) 
    ->method('handleValue') 
    ->with('bla'); 

Eventuali altri metodi nella classe non specificato nella matrice si dà setMethods() sarà eseguito come è. Se lo non utilizza utilizza setMethods tutti i metodi restituiranno NULL a meno che non vengano impostati in modo specifico.