Tester des callbacks avec PHPUnit

Date de publication : — dernière modification :

Faire des tests unitaires est une très bonne pratique quand on développe. Cela permet de déceler très rapidement le dysfonctionnement d'une partie de son code, et de pouvoir corriger le bug très facilement.

Il arrive parfois d'avoir besoin de tester le bon fonctionnement d'un callback en PHP, comme dans cet exemple :

<?php
class SuperClasse {
    public function appellerCallback($callback) {
        call_user_func_array($callback);
    }
}
?>

Et voici le test de base pour cet exemple :

<?php
class SuperClasseTest extends PHPUnit_Framework_TestCase {
    public function testAppellerCallback() {
        $super_classe = new SuperClasse();

        // Définir callback de sorte à vérifier à ce que la méthode "superFonction" de l'objet en paramètre soit appellée

        $super_classe->appellerCallback($monCallback);
    }
}
?>

Pour tester ce cas dans PHPUnit, on va se servir des Mocks. Ce sont des structures qui permettent de simuler le fonctionnement d'un objet. On les utilise notamment pour simuler les interactions avec une base de donnée. Ici, nous nous en servirons pour savoir si une méthode de la classe a été appellée ou non.

Le mock se définit ainsi :

<?php
class SuperClasseTest extends PHPUnit_Framework_TestCase {
    public function testAppellerCallback() {
        $super_classe = new SuperClasse();

        $monCallback = $this->getMock('stdClass',array('superFonction'));

        $super_classe->appellerCallback([$monCallback,'superFonction']);
    }
}
?>

On vient d'indiquer qu'il est de type stdClass, et qu'il a une méthode nommée superFonction (celle qu'on veut tester). On va maintenant configurer ce mock :

<?php
class SuperClasseTest extends PHPUnit_Framework_TestCase {
    public function testAppellerCallback() {
        $super_classe = new SuperClasse();

        $monCallback = $this->getMock('stdClass',array('superFonction'));
        $monCallback->expects($this->once())
                    ->method('superFonction')
                    ->will($this->returnValue(true));

        $super_classe->appellerCallback([$monCallback,'superFonction']);
    }
}
?>

On a indiqué par le ->expects($this->once()) que le callback devra être appellé une seule fois pour obtenir l'assertion, et par ->will($this->returnValue(true)) on demande à ce que le callback retourne la value true quand il sera appellé.

Cas complexe : quand on veut tester l'appel à une méthode statique

Cette fois-ci, c'est un peu plus corsé. On aimerait tester l'utilisation d'une méthode statique comme callback.

Pour ce faire, on va créer une classe, qui quand elle sera appellée va appeller à son tour le mock.

<?php
class MockProxy {
    private static $mock = null;

    public static function setMock($mock) {
        self::$mock = $mock;
    }

    public static function __callStatic($name,$args) {
        call_user_func_array([self::$mock,$name],$args);
    }
}
?>

On va toujours utiliser notre mock, mais on va utiliser un proxy qui va simuler une classe ayant une méthode statique. On aura donc le code de test suivant :

<?php
class SuperClasseTest extends PHPUnit_Framework_TestCase {
    public function testAppellerCallbackStatique() {
        $super_classe = new SuperClasse();

        $mock = $this->getMock('stdClass',array('superFonction'));
        $mock->expects($this->once())
             ->method('superFonction')
             ->will($this->returnValue(true));

        MockProxy::setMock($mock);

        $super_classe->appellerCallback(['MockProxy','superFonction']);
    }
}
?>