Tester l'API REST Symfony avec PHPUnit

Comme dans tout projet logiciel, il est primordial de supporter le développement de fonctionnalités par une suite de tests solide. Le logiciel devra évoluer et le code devra être modifié (réusinage), pour ces deux raisons, un filet de sécurité est essentiel : les tests.

Il est possible d’écrire des tests unitaires et fonctionnels. Dans cet article, des tests fonctionnels seront mis en place. Il sont plus lents, mais permettront de tester l’application de point à point. Dans ces tests, un crawler sera initialisé et lancera des requêtes de types GET, POST, PUT et DELETE sur notre API REST. Par la suite, il sera possible de vérifier que l’API REST réagisse correctement aux requêtes lancées avec des vérifications (assert) fournies par PHPUnit.

Tests fonctionnels pour un API REST

La première méthode rencontrée est setUp(), elle permet de réinitialiser chaque test dans un état connu. Dans le cas qui nous concerne, les fixtures seront injectées dans la base de données.

/src/Acme/Bundle/ApiBundle/Tests/Controller/Rest/CommentController.php

<?php
namespace Acme\Bundle\ApiBundle\Tests\Controller\Rest;
use Acme\Bundle\ApiBundle\Tests\WebTestCase;

/**
* CommentControllerTest
* @author G. Simard
*/

class CommentControllerTest extends WebTestCase
{
  public function setUp()
  {
    parent::setup();
    $fixtures = array(
      'AppBundle\DataFixtures\ORM\LoadUserData',
      'Acme\Bundle\ApiBundle\DataFixtures\ORM\LoadUserCommentData',
    );
    $this->loadFixtures($fixtures);
  }
}

Tester la sécurité

La méthode testGetCommentsWithoutToken() lance une requête GET afin de vérifier qu’une ressource est bien protégée, le serveur devrait retourner un code 401 (Unauthorized).

/src/Acme/Bundle/ApiBundle/Tests/Controller/Rest/CommentController.php

<?php
...
  public function testGetCommentsWithoutToken()
  {
    $client = static::createClient();
    $this->execQuery($client, 'GET', null, '/api/comments');
    $response = $client->getResponse();
    $this->assertJsonResponse($response, 401);
  }
...
}

Tester la réponse GET de l’API REST

La méthode testGetComments() lance une requête GET afin de vérifier que la réponse du serveur est celle attendue, le serveur devrait retourner un objet Comment sérialisé (JSON) ainsi que le code 200.

Avant de faire cette requête, un client authentifié est créé avec la méthode createAuthenticatesClient(), ce client aura en sa possession un JWT valide.

/src/Acme/Bundle/ApiBundle/Tests/Controller/Rest/CommentController.php

<?php
...
    public function testGetComments()
    {
        $client = $this->createAuthenticatedClient('api@api.com', 'api');
        $this->execQuery($client, 'GET', null, '/api/comments');
        $response = $client->getResponse();
        $this->assertJsonResponse($response, 200);

        $content = json_decode($response->getContent(), true);
        $this->assertInternalType('array', $content);
        $this->assertCount(3, $content);

        $comment = $content[0];
        $this->assertArrayHasKey('body', $comment);
        $this->assertArrayHasKey('status', $comment);
        $this->assertArrayNotHasKey('content', $comment);
        $this->assertArrayNotHasKey('password', $comment['user']);
    }
...

Tester le bon fonctionnement d’une requête POST

Cette fois-ci, une requête POST est envoyée. On vérifier que le résultat est celui attendu et accompagné d’un code 201 (Created)

/src/Acme/Bundle/ApiBundle/Tests/Controller/Rest/CommentController.php

<?php
...
 public function testPostNewComment()
    {
        $data = array
        (
            "body"  => "This is a comment",
            "status"  => "1",
            "user_id" => "1",
            "movie_id"  => "4",
        );

        $client = $this->createAuthenticatedClient('api@api.com', 'api');
        $this->postData($client, $data, '/api/comments');
        $response = $client->getResponse();
        $this->assertStatusCodeResponse($response, 201);
        $comment = json_decode($response->getContent(), true);

        $this->assertInternalType('array', $comment);
        $this->assertArrayHasKey('id', $comment);
        $this->assertArrayHasKey('movie_id', $comment);
        $this->assertArrayHasKey('body', $comment);
        $this->assertArrayNotHasKey('password', $comment['user']);
    }
...

Tester la robustesse et les validations

Il est aussi important de vérifier que nos règles de validations sont bonnes. Dans le cas suivant, on s’assure que le code 410 est retourné. Il serait aussi bon de vérifier que le message retourné est celui attendu.

<?php
...
    public function testPostNewCommentWithoutMovieId()
    {
        $data = array
        (
            "body" => 'My new simple comment',
        );

        $client = $this->createAuthenticatedClient('api@api.com', 'api');
        $this->postData($client, $data, '/api/comments');

        $response = $client->getResponse();
        $this->assertStatusCodeResponse($response, 410);
    } 
...

Exécuter les tests pour votre API REST

Vous pouvez lancer l’exécution de vos tests avec la commande suivante :

$ phpunit

Avant Symfony 2.8, vous devrez utiliser la variante suivante :

$ phpunit -c app

PHPUnit en ligne de commande

Arrêter les tests lorsque ça va mal

Aussitôt qu’une erreur est rencontrée, PHPUnit affichera le message d’erreur.

$ phpunit --stop-on-failure
$ phpunit --stop-on-error
$ phpunit --stop-on-erro

Exécuter quelques tests seulement

Il est possible de filtrer les tests à exécuter de la manière suivante :

$ phpunit --filter Comment
$ phpunit --filter "Comment|Fav|Auth"
$ phpunit --filter 'TestNamespace\\TestCaseClass::testMethod'

https://phpunit.de/manual/current/en/textui.html

Afin de filtrer les tests à exécuter, il est aussi possible de modifier le fichier phpunit.xml et de créer des suites :

<testsuites>
  <testsuite name="Project Test Suite">
    <directory>../src/*/*Bundle/Tests</directory>
    <directory>../src/*/Bundle/*Bundle/Tests</directory>
    <directory>../src/*Bundle/Tests</directory>
  </testsuite>
</testsuites>

Modifier le format de sortie des tests

De base, PHPUnit n’affiche pas le titre des tests, mais le caractère point. Afin d’afficher les titres des tests, il est possible d’utiliser –testdox :

$ phpunit --testdox
PHPUnit 5.0.10 by Sebastian Bergmann and contributors.

Acme\Bundle\ApiBundle\Tests\Controller\Rest\CommentController
 [x] Get comments without token
 [x] Get comments
 [x] Get comments with movie id param
 [x] Get comments with empty movie id param
 [ ] Get comment
 [x] Get comment with invalid role
 [x] Get invalid comment

Plutôt que :

$ phpunit
PHPUnit 5.0.10 by Sebastian Bergmann and contributors.

................................................
..............

Couverture du code

PHPUnit offre aussi le possibilité de générer un rapport sur la couverture de code par vos tests.

 --coverage-html

Vider la cache

Note

Même lors de l’exécution des tests, Symfony utilise un système de cache. Si les tests ne s’exécutent pas comme prévu et que les résultats sont louches, il faut peut-être vider la cache avec l’option –env=test.

 

# Vide la cache pour l'environnement test
$ bin/console cache:clear --env=test

# Vide la cache pour l'environnement dev
$ bin/console cache:clear

Pre-commit hook

Il est possible de configurer git afin d’exécuter un script lors d’un commit avec un pre-commit hook. Il suffit de créer le script et de l’activer en le déposant dans le dossier .git/hook.

Exemple de hook shell

Lance les tests pour un projet Symfony >= 2.8 :

#!/bin/sh
phpunit

Lance les tests pour un projet Symfony < 2.8 :

#!/bin/sh
phpunit -c /CHEMIN_VERS_SYMFONY/app

Exemple de hook php

Il est aussi possible de créer un script php.

#!/usr/bin/env php
<?php
$projectName = basename(getcwd());
exec('vendor/bin/phpunit', $output, $returnCode);
if ($returnCode !== 0) {
  $minimalTestSummary = array_pop($output);
  printf("Test suite for %s failed: ", $projectName);
  printf("( %s ) %s%2\$s", $minimalTestSummary, PHP_EOL);
  printf("ABORTING COMMIT!\n");
  exit(1);
}
exit(0);

Activer un hook

Afin d’activer un hook, il suffit de créer un lien symbolique dans le dossier caché .git/hooks.

$ ln -s ../vendor/hgtan/symfony-pre-commit/hooks .git/hooks

Hook pour un projet Symfony

Un petit extra pour les projets Symfony.

https://github.com/hoangthienan/symfony-pre-commit

Facebook Comments

0 réponses

Laisser un commentaire

Rejoindre la discussion?
N’hésitez pas à contribuer !

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur la façon dont les données de vos commentaires sont traitées.