Dans le chapitre précédent, nous avons vu comment :
écrire le test permettant de s'assurer qu'un objet
$user
de typeApp\Entity\User
est bien retourné ;créer une classe de test
GithubUserProviderTest
dans le dossiertests/Security
.
Maintenant, nous allons aller plus loin en :
Écrivant le test qui couvre le cas où aucune information ne serait récupérée après la requête HTTP à GitHub.
Factorisant le code.
Écrivez le test couvrant le cas où aucune information ne serait récupérée après la requête HTTP à GitHub
Voici un cas qu’il nous reste à couvrir dans la méthode loadUserByUsername
.
Vous avez toutes les clés pour y arriver ! Essayez seul avant de regarder la correction.
Alors ?
Allez, on vous donne la correction :
<?php
namespace App\Tests\Security;
// …
use App\Security\GithubUserProvider;
class GithubUserProviderTest extends TestCase
{
// …
public function testLoadUserByUsernameThrowingException()
{
$client = $this->getMockBuilder('GuzzleHttp\Client')
->disableOriginalConstructor()
->setMethods(['get'])
->getMock();
$serializer = $this
->getMockBuilder('JMS\Serializer\Serializer')
->disableOriginalConstructor()
->getMock();
$response = $this
->getMockBuilder('Psr\Http\Message\ResponseInterface')
->getMock();
$client
->expects($this->once()) // Nous nous attendons à ce que la méthode get soit appelée une fois
->method('get')
->willReturn($response);
$streamedResponse = $this
->getMockBuilder('Psr\Http\Message\StreamInterface')
->getMock();
$response
->expects($this->once()) // Nous nous attendons à ce que la méthode getBody soit appelée une fois
->method('getBody')
->willReturn($streamedResponse);
$streamedResponse
->expects($this->once())
->method('getContents')
->willReturn('foo');
$serializer
->expects($this->once()) // Nous nous attendons à ce que la méthode deserialize soit appelée une fois
->method('deserialize')
->willReturn([]);
$this->expectException('LogicException');
$githubUserProvider = new GithubUserProvider($client, $serializer);
$githubUserProvider->loadUserByUsername('an-access-token');
}
}
Il suffit de faire en sorte que le double (stub) du serializer
retourne un tableau vide, et le tour est joué !
Allez, on arrive au bout, il ne vous reste plus qu’à factoriser le code.
Factorisez le code
Vous avez remarqué que bon nombre des doublures sont utilisées dans les deux tests unitaires. Il est possible de les initialiser à chaque début de test grâce à la méthode setUp
.
Voyons tout cela en vidéo :
La méthode setUp
La méthode setUp est une méthode provenant de la classe PHPUnit_Framework_TestCase
, que l'on peut surcharger pour exécuter des instructions avant chaque test de la classe.
Vous allez donc faire en sorte que toutes les doublures utilisées dans les tests unitaires soient initialisées à chaque début de test (méthode de test).
Voici le code remis au propre après cette étape de factorisation :
<?php
namespace App\Tests\Security;
use App\Entity\User;
use App\Security\GithubUserProvider;
use GuzzleHttp\Client;
use JMS\Serializer\Serializer;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
class GithubUserProviderTest extends TestCase
{
private MockObject | Client | null $client;
private MockObject | Serializer | null $serializer;
private MockObject | StreamInterface | null $streamedResponse;
private MockObject | ResponseInterface | null $response;
public function setUp(): void
{
$this->client = $this->getMockBuilder('GuzzleHttp\Client')
->disableOriginalConstructor()
->setMethods(['get'])
->getMock();
$this->serializer = $this
->getMockBuilder('JMS\Serializer\Serializer')
->disableOriginalConstructor()
->getMock();
$this->streamedResponse = $this
->getMockBuilder('Psr\Http\Message\StreamInterface')
->getMock();
$this->response = $this
->getMockBuilder('Psr\Http\Message\ResponseInterface')
->getMock();
}
public function testLoadUserByUsernameReturningAUser()
{
$this->client
->expects($this->once())
->method('get')
->willReturn($this->response);
$this->response
->expects($this->once())
->method('getBody')
->willReturn($this->streamedResponse);
$this->streamedResponse
->expects($this->once())
->method('getContents')
->willReturn('foo');
$userData = [
'login' => 'a login',
'name' => 'user name',
'email' => 'adress@mail.com',
'avatar_url' => 'url to the avatar',
'html_url' => 'url to profile'
];
$this->serializer
->expects($this->once())
->method('deserialize')
->willReturn($userData);
$githubUserProvider = new GithubUserProvider($this->client, $this->serializer);
$user = $githubUserProvider->loadUserByUsername('an-access-token');
$expectedUser = new User($userData['login'], $userData['name'], $userData['email'], $userData['avatar_url'], $userData['html_url']);
$this->assertEquals($expectedUser, $user);
$this->assertEquals('App\Entity\User', get_class($user));
}
public function testLoadUserByUsernameThrowingException()
{
$this->client
->expects($this->once())
->method('get')
->willReturn($this->response);
$this->response
->expects($this->once())
->method('getBody')
->willReturn($this->streamedResponse);
$this->streamedResponse
->expects($this->once())
->method('getContents')
->willReturn('foo');
$this->serializer
->expects($this->once())
->method('deserialize')
->willReturn([]);
$this->expectException('LogicException');
$githubUserProvider = new GithubUserProvider($this->client, $this->serializer);
$githubUserProvider->loadUserByUsername('an-access-token');
}
}
La méthode tearDown
Il est également possible d'intervenir à chaque fin de test (après chaque méthode de test de la classe) grâce à la méthode tearDown
.
Pour cela, il vous faut mettre les propriétés à null
à nouveau :
<?php
namespace App\Tests\Security;
// …
class GithubUserProviderTest extends TestCase
{
// …
public function tearDown() : void
{
$this->client = null;
$this->serializer = null;
$this->streamedResponse = null;
$this->response = null;
}
}
En résumé
Faire attention à ce que l’on double, afin de bien maîtriser nos doublures pour contrôler leur comportement de A à Z.
Un test unitaire peut prendre du temps au début, mais en fait gagner beaucoup par la suite.
Tester son code permet d'appréhender une première fois un code inconnu.
Nous en avons fini avec cette première partie ! Après le quiz, rendez-vous au premier chapitre de la partie 2, nous répondrons aux questions que vous devez vous poser lorsque vous écrivez des tests unitaires.