web-dev-qa-db-fra.com

méthode de test de phpunit qui appelle d'autres méthodes de classe qui ont besoin d'une simulation

J'essaie de créer un test unitaire assez standard où j'appelle une méthode et affirme sa réponse, mais la méthode que je teste appelle une autre méthode à l'intérieur de la même classe qui fait un peu de travail lourd.

Je veux simuler cette méthode, mais toujours exécuter la méthode que je teste telle quelle, uniquement avec la valeur simulée renvoyée par l'appel à l'autre méthode.

J'ai abrégé l'exemple pour le rendre aussi simple que possible.

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;

    }

}

Ma tentative d'écrire les tests.

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

    }

}

J'ai essayé de se moquer de l'objet MyClass, mais quand je le fais et que j'appelle la méthode de test, il renvoie toujours null. J'ai besoin d'un moyen de se moquer de la seule méthode mais de laisser le reste de l'objet intact.

24
greg

Vous pouvez simuler la classe que vous testez et spécifier la méthode que vous souhaitez simuler.

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

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

Cependant, l'OMI ce n'est pas la meilleure idée pour vos tests. Des tests comme celui-ci rendront la refactorisation beaucoup plus difficile. Vous spécifiez l'implémentation de la classe plutôt que le comportement que la classe est censée avoir. Si handleValue fait beaucoup de travail compliqué qui rend les tests difficiles, envisagez de déplacer la logique dans une classe distincte et de l'injecter dans votre classe. Ensuite, vous pouvez créer une maquette de cette classe et la transmettre à testMethod. Cela vous donnera l'avantage supplémentaire de rendre MyClass plus extensible si handleValue a besoin d'adapter son comportement.

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

En règle générale, vous ne devez pas vous moquer du système que vous testez.

25
Schleis

Vous pouvez spécifier les méthodes à simuler (maquette partielle) avec 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');

Toutes les autres méthodes de la classe non spécifiées dans le tableau que vous donnez setMethods() seront exécutées telles quelles. Si vous ne le faites pas utilisez setMethods toutes les méthodes renverront NULL sauf si vous les définissez spécifiquement.

12
greg