Nous allons parler tests unitaires aujourd'hui et plus précisément nous attarder sur l'utilisation de Prophecy. Créée à l'origine pour PHPSpec le système de Prophecy a été intégré à PHPUnit dans la version 4.5 et propose une alternative au système de mocks traditionels.

MockBuilder vs Prophecy

Les 2 méthodes proposent une manière un peu différente de faire les choses.

/**
* MockBuilder
**/
// On crée un "double" de l'objet
$session = $this->getMockBuilder(SessionManager::class)
  ->disableOriginalConstructor()
  ->setMethods(['write', 'delete'])
  ->getMock();
// On branche nos attentes, et on stub une ou plusieurs méthodes si besoins
$session->expect($this->once())
  ->method('write')
  ->with($this->equalTo('user'))
  ->will($this->returnValue(1));

/**
* Prophecy
**/
// On crée une "prophetie"
$sessionProphecy = $this->prophesize(SessionManager::class);
// On décrit ce qui doit se réaliser
$sessionProphecy
  ->write('user')
  ->shouldBeCalled()
  ->willReturn(1);
// On récupère notre "double"
$session = $sessionProphecy->reveal();

Les Mocks dans PHPUnit visent à créer un double d'une classe en modifiant le comportement de certaines méthodes si besoin (pour éviter des appels à une API externe par exemple). Il est ensuite possible de greffer des attentes afin de vérifier qu'une méthode est correctement appelée avec de bon paramètres.
Avec Prophecy on va chercher à décrire le comportement attendu d'un objet en décrivant les méthodes qu'il devra recevoir et les résultats qu'elles devront retourner.

La principale différence est qu'il n'est pas possible de remplacer seulement certaines méthodes avec les prophecy. Si une méthode qui n'a pas été "prophétisée" est appelée une erreur sera alors renvoyée. Si votre but est simplement de créer un double d'une classe en ne remplaçant quelques méthodes il ne faudra pas utiliser les prophecy.

Fonctionnement de Prophecy

Lorsque vous utilisez la méthode reveal() le système va créer un double de votre objet.

$auth = $this->prophesize(Auth::class)->reveal();
$auth->getUser(); // null

Par defaut ce double est un "dummy", toutes les méthodes renvoient null lorsqu'elles sont appelées. Il est possible de créer un stub pour remplacer le comportement d'une méthode.

$authProphecy = $this->prophesize(Auth::class);
$authProphecy->getUser()->willReturn(1);
$authProphecy->reveal()->getUser(); // 1

En revanche, une erreur sera renvoyée si une méthode différente est appélée ou si un appel à la méthode est fait avec des arguments différents.

$authProphecy = $this->prophesize(Auth::class);
$authProphecy->getUser()->willReturn(1);
$authProphecy->reveal()->getUser('auth'); // Erreur : Méthode inattendue 
$authProphecy->reveal()->getSession(); // Erreur : Méthode inattendue 

Etendre Prophecy

Lors du test d'argument ou lors d'un retour il est possible d'appliquer des comportements plus spécifiques :

$this->stripe->createCharge(new Argument\Token\LogicalAndToken([
    Argument::withEntry('amount', 6000),
    Argument::withEntry('source', $card->id)
]))->shouldBeCalled()
    ->will(new Prophecy\Promise\ReturnPromise([$charge1, $charge2]));

Pour tester l'argument d'une méthode vous pouvez utiliser la valeur attendue ou utiliser une classe implémentant la TokenInterface. Par défaut, prophecy propose une collection de classes qui permettent de gérer la plupart des cas. Mais pour des besoins spécifiques vous pouvez créer votre propre classe en implémentant la TokenInterface.
De la même façon vous pouvez aussi spécifier le retour d'une méthode gràce à une Promise.