Si vous découvrez le projet, je vous invite à commencer par ici, je pense que ce sera mieux pour vous plutôt que de prendre en cours de route.
Pour les fidèles, on retrouve notre road map habituelle :

Road map du projet

  1. Pré-requis et architecture
  2. Configuration de Silex
  3. Jouons avec Silex
  4. Ecriture de tests fonctionnels
  5. TDD pour le code métier
  6. Templating

J'espère que vous avez bien apprécié l'article précédent car aujourd'hui, on va avancer le projet (un peu). Et pour cela, je ne sais pas du tout si vous avez travaillé dans le même répertoire que le projet mais étant donné que c'était mon cas, je vais devoir revenir sur ma copie Github.

Si vous n'aviez pas encore de working copy, voici la commande à suivre (supposant que vous êtes déjà dans le répertoire dev) :

git clone --no-checkout git@github.com:eexit/Portfolio.git
git checkout 2cc4529a1345b6cfbd34
git submodule update --init --recursive
curl https://silex-project.org/get/silex.phar -o vendor/Silex/silex.phar

Pour ceux qui avaient déjà une working copy, un petit reset sera de bon augure :

git reset --hard 2cc4529a1345b6cfbd34
git submodule update --recursive
php vendor/Silex/silex.phar update

Hop, nous voici de nouveau prêt à bosser.

Note : désormais, vous aurez l'occasion d'avancer pas à pas en faisant des checkout sur les fichiers si vous souhaitez partir d'un point bien défini de l'article. Je ne vous conseil pas de faire un checkout global car mes modifications ne vont pas forcément dans le même ordre que la rédaction de mes articles.

Pour checkouter un fichier au commit xxxxxxxxx :

git checkout xxxxxxxxx path/to/file

4. Écriture de tests fonctionnels

Ici aussi, on va faire un petit sous-menu pour vous simplifier l'accès aux sections importantes de la page :

  • Le Client et le Crawler de Symfony 2
  • Structure des tests fonctionnels
  • Tests sur l'arborescence du site
  • Tests sur le DOM
  • BONUS: Tester un formulaire
    • Installation de l'extension Validator
    • Tests sur la structure DOM du formulaire
    • Tests sur la validation du formulaire

Le Client et le Crawler de Symfony 2

Comme vous le savez déjà, le cœur de Symfony 2 est entièrement basé sur le protocole HTTP ce qui implique donc une notion de requête, de réponse mais aussi de client ainsi que de DOMCrawler afin d'analyser le code HTML renvoyé par la réponse dans le cadre de tests fonctionnels (fourni avec le composant BrowserKit). Attention, les deux derniers composants ne font pas parti du protocole HTTP mais sont en corrélation avec notre contexte d'utilisation du framework.
Donc durant cette phase de tests fonctionnels, nous allons jouer un peu avec ces notions mais dans un but de faire du TDD fonctionnel.

Le Client

Il simule un navigateur Web et permet de faire des actions comme cliquer sur des boutons, soumettre un formulaire, naviguer dans l'historique et les cookies, et bien entendu de faire des requêtes et de récupérer les réponses HTTP associées.
A chaque requête, une réponse est fournie et si la réponse est au format XML/HTML, alors un Crawler est lui aussi disponible automatiquement.

Le Crawler

Il permet de parser l'arbre DOM de la réponse XML/HTML renvoyée par Silex. En utilisant l'API DOM de PHP, le Crawler permet d'accéder aux nœuds ainsi qu'à leur contenu, parent, enfant, etc. avec une syntaxe semblable à celle des sélecteurs jQuery mais aussi grâce au requêtes XPath si vous avez des nœuds très spécifiques à remonter.

Structure des tests fonctionnels

Sans perdre de temps, on va rentrer dans le vif du sujet et on va tout de suite s'amuser avec les tests fonctionnels. Pour cela, vous devez installer PHPUnit (si ça n'est pas déjà le cas) et vous allez créer les fichiers suivants :

touch phpunit.xml tests/bootstrap.php tests/WebAppTreeTest.php

Le code source à placer dans le bootstrap :

<?php
// bootstrap.php
 
// Loads Silex for Tests
require_once __DIR__ . '/../vendor/Silex/silex.phar';

bootstrap.php @commit 1405c2e9e55b66492f88

Maintenant le fichier de configuration de PHPUnit :

<!-- phpunit.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
    backupStaticAttributes="false"
    colors="true"
    convertErrorsToExceptions="true"
    convertNoticesToExceptions="true"
    convertWarningsToExceptions="true"
    processIsolation="false"
    stopOnFailure="false"
    stopOnError="false"
    stopOnIncomplete="false"
    stopOnSkipped="false"
    syntaxCheck="false"
    bootstrap="tests/bootstrap.php">
    <testsuites>
        <testsuite name="Portfolio Test Suite">
            <directory>./tests/</directory>
        </testsuite>
    </testsuites>
</phpunit>

phpunit.xml @commit 1405c2e9e55b66492f88

Et enfin notre premier fichier de test avec lequel nous allons tester l'arborescence de notre site Web :

<?php
 
use Silex\WebTestCase;
 
class WebAppTreeTest extends WebTestCase
{
    public function createApplication()
    {
        return require __DIR__ . '/../src/portfolio.php';
    }
 
    public function testLayoutElements()
    {
        $this->markTestIncomplete('Not yet implemented');
    }
 
    public function testGetIndex()
    {
        $this->markTestIncomplete('Not yet implemented');
    }
 
    public function testGetAbout()
    {
        $this->markTestIncomplete('Not yet implemented');
    }
 
    public function testGetSets()
    {
        $this->markTestIncomplete('Not yet implemented');
    }
 
    public function testGetSetNotExists()
    {
        $this->markTestIncomplete('Not yet implemented');
    }
 
    public function testGetSetExists()
    {
        $this->markTestIncomplete('Not yet implemented');
    }
 
    public function testGetGalleryNotExists()
    {
        $this->markTestIncomplete('Not yet implemented');
    }
 
    public function testGetGalleryExists()
    {
        $this->markTestIncomplete('Not yet implemented');
    }
}

Enfin, on lance les tests pour obtenir ce résultat :

phpunit
PHPUnit 3.5.14 by Sebastian Bergmann.

IIIIIIII

Time: 1 second, Memory: 12Mb

OK, but incomplete or skipped tests!
Tests: 8, Assertions: 0, Incomplete: 8.

Tests sur l'arborescence du site

Maintenant que nous avons mis en place notre environnement de test (et que ça fonctionne), je vous propose de commencer à remplir les différents tests afin d'agrémenter notre petite application Silex.
Doucement mais sûrement, nous allons dans un premier temps écrire des tests simples afin de déterminer si notre application répond bien aux requêtes que nous attendons.

Voici donc les premiers tests basiques :

<?php
 
use Silex\WebTestCase;
 
class WebAppTreeTest extends WebTestCase
{
    public function createApplication()
    {
        return require __DIR__ . '/../src/portfolio.php';
    }
 
    public function testLayoutElements()
    {
        $this->markTestIncomplete('Not yet implemented');
    }
 
    public function testGetIndex()
    {
        $client = $this->createClient();
        $crawler = $client->request('GET', '/');
        $this->assertTrue($client->getResponse()->isOk());
    }
 
    public function testGetAbout()
    {
        $client = $this->createClient();
        $client->request('GET', '/about.html');
        $this->assertTrue($client->getResponse()->isOk());
    }
 
    public function testGetSets()
    {
        $client = $this->createClient();
        $client->request('GET', '/sets.html');
        $this->assertTrue($client->getResponse()->isOk());
    }
 
    public function testGetSetNotExists()
    {
        $this->markTestSkipped('Missing business code layer!');
 
        $client = $this->createClient();
        $client->request('GET', '/set/foo.html');
        $this->assertTrue($client->getResponse()->isNotFound());
    }
 
    public function testGetSetExists()
    {
        $client = $this->createClient();
        $client->request('GET', '/travels.html');
        $this->assertTrue($client->getResponse()->isOk());
    }
 
    public function testGetGalleryNotExists()
    {
        $this->markTestSkipped('Missing business code layer!');
 
        $client = $this->createClient();
 
        $client->request('GET', '/travels.html');
        $this->assertTrue($client->getResponse()->isOk());
 
        $client->request('GET', '/travels/foo.html');
        $this->assertTrue($client->getResponse()->isNotFound());
    }
 
    public function testGetGalleryExists()
    {
        $client = $this->createClient();
 
        $client->request('GET', '/travels.html');
        $this->assertTrue($client->getResponse()->isOk());
 
        $client->request('GET', '/travels/chile.html');
        $this->assertTrue($client->getResponse()->isOk());
    }
}

WebAppTreeTest.php @commit dc58b8674e235c442f48

Vous lancez votre campagne de tests :

phpunit
PHPUnit 3.5.14 by Sebastian Bergmann.

I.FFSFSF

Time: 1 second, Memory: 11.75Mb

There were 4 failures:

1) WebAppTreeTest::testGetAbout
Failed asserting that  is true.

/../net/eexit/photography/dev/tests/WebAppTreeTest.php:28

2) WebAppTreeTest::testGetSets
Failed asserting that  is true.

/../net/eexit/photography/dev/tests/WebAppTreeTest.php:35

3) WebAppTreeTest::testGetSetExists
Failed asserting that  is true.

/../net/eexit/photography/dev/tests/WebAppTreeTest.php:51

4) WebAppTreeTest::testGetGalleryExists
Failed asserting that  is true.

/../net/eexit/photography/dev/tests/WebAppTreeTest.php:72

FAILURES!
Tests: 8, Assertions: 5, Failures: 4, Incomplete: 1, Skipped: 2.
zsh: exit 1     phpunit

La plupart des tests ont échoué et c'est normal... Maintenant, il faut compléter notre portfolio.php :

<?php
// portfolio.php
 
$app->get('/about.html', function() use ($app) {
    return $app['twig']->render('about.twig');
});
 
$app->get('/sets.html', function() use ($app) {
    return $app['twig']->render('sets.twig');
});
 
$app->get('/{set}.html', function($set) use ($app) {
    return $app->escape($set);
});
 
$app->get('/{set}/{gallery}.html', function($set, $gallery) use ($app) {
    return $app->escape($set) . '/' . $app->escape($gallery);
});

portfolio.php @commit dc58b8674e235c442f48

On relance PHPUnit et on obtient le résultat suivant :

phpunit
PHPUnit 3.5.14 by Sebastian Bergmann.

I...S.S.

Time: 0 seconds, Memory: 12.00Mb

OK, but incomplete or skipped tests!
Tests: 8, Assertions: 6, Incomplete: 1, Skipped: 2.

Avec ces premiers tests unitaires, nous venons de tester nos routes très simplement. Mais les tests fonctionnels ne se limitent pas qu'à la navigation sur un site Web, on pourrait utiliser twill (ouais je sais c'est vieux) pour cela.

Nous voulons aussi tester le contenu de nos pages Web et vérifier l'existence de certaines balises ou certains contenus.

Tests sur le DOM

Dans cette partie de l'article, on va utiliser le DomCrawler de Symfony pour s'amuser à rechercher Charlie repérer nos éléments HTML.
La prise en main est presque aussi simple qu'avec jQuery puisque les sélecteurs sont (presque) les même. On peut facilement arriver à quelque chose de propre sans trop se prendre la tête.

Ici, pour l'article, je ne vais vous présenter que les tests pour le layout de l'application (layout.twig) et le reste viendra au fur et à mesure. Pour cela, on reprend le focus sur le fichier WebAppTreeTest.php et on implémente le premier test :

<?php
 
use Silex\WebTestCase;
 
class WebAppTreeTest extends WebTestCase
{
    const NB_LINKS_LAYOUT = 4;
 
    public function createApplication()
    {
        return require __DIR__ . '/../src/portfolio.php';
    }
 
    public function testLayoutElements()
    {
        $client = $this->createClient();
        $crawler = $client->request('GET', '/');
        $this->assertEquals(1, $crawler->filter('title:contains("Portfolio")')->count());
        $this->assertEquals(1, $crawler->filter('meta[http-equiv="content-type"]')->count());
        $this->assertEquals(1, $crawler->filter('meta[charset]')->count());
        $this->assertEquals(1, $crawler->filter('meta[name="author"]')->count());
        $this->assertEquals(1, $crawler->filter('meta[name="description"]')->count());
        $this->assertEquals(3, $crawler->filter('head > link')->count());
        $this->assertEquals(3, $crawler->filter('header > nav > ul > li')->count());
        $this->assertEquals(1, $crawler->filter('footer')->count());
        $this->assertEquals(self::NB_LINKS_LAYOUT, count($crawler->filter('a')->links()));
 
        // Tests the eexit.net back link
        $client->click($crawler->selectLink('eexit.net')->link());
        $this->assertTrue($client->getResponse()->isOk());
        $this->assertEquals('https://www.eexit.net/', $client->getRequest()->getUri());
    }

WebAppTreeTest.php @commit ac8b264d28536b623996

Comme vous pouvez le voir, on test de manière assez précise les éléments voulus tout en utilisant toujours les assertions offertes par PHPUnit.

Note : l'écriture de tests est longue et fastidieuse... Il est très important d'identifier la granularité d'importance de vos tests car un test trop succinct est inutile alors qu'un test trop riche est une perte de temps. Testez uniquement le strict nécessaire !

Note 2 : il est clair que dans mon précédent exemple, j'ai un peu poussé le test car j'apprends à me servir du Crawler.

Si vous lancez votre PHPUnit après cela, vous vous doutez bien que le premier test va échouer :

phpunit
PHPUnit 3.5.14 by Sebastian Bergmann.

F...S.S.

Time: 0 seconds, Memory: 12.75Mb

There was 1 failure:

1) WebAppTreeTest::testLayoutElements
Failed asserting that  matches expected .

/../net/eexit/photography/dev/tests/WebAppTreeTest.php:19

FAILURES!
Tests: 8, Assertions: 9, Failures: 1, Skipped: 2.
zsh: exit 1     phpunit

Foncez donc pour ajouter le code manquant à votre layout.twig :

{# layout.twig #}
<!doctype html>
<html lang="en">
    <head>
        <meta http-equiv="content-type" content="text/html;charset=utf-8">
        <meta charset="utf-8">
        <title>Portfolio</title>
        <meta name="author" content="">
        <meta name="description" content="">
        <link rel="shortcut icon" href="">
        <link rel="stylesheet" href="" type="text/css" media="screen">
        <link rel="stylesheet" href="" type="text/css" media="handheld">
    </head>
    <body>
        <header>
            <a href="/"><img src="" width="" height="" alt="logo" /></a>
            <nav>
                <ul>
                    <li><a href="https://www.eexit.net">eexit.net</a></li>
                    <li><a href="/about.html" rel="me">About</a></li>
                    <li><a href="/contact.html">Contact</a></li>
                </ul>
            </nav>
        </header>
        {% block body %}{% endblock %}
        <footer>
        </footer>
    </body>
</html>

layout.twig @commit c47a22e9a0aa01be543e

Normalement vos tests unitaires devraient passer après cela.

A un moment, j'ai voulu arrêter l'article à ce niveau mais j'ai eu la bonne idée de vouloir partager plus avec vous... On continue ?

BONUS: Tester un formulaire

Après une petite réflexion, j'ai décidé de rajouter un formulaire de contact dans mon projet. Histoire de jouer un peu avec les validateurs de Symfony mais aussi d'apprendre comment rendre un formulaire testable avec les outils que proposent Silex/Symfony.

Installation de l'extension Validator

Vous l'aurez compris, Symfony propose une belle panoplie de classes pour valider tout et n'importe quoi donc on ne va pas se gêner pour les utiliser, surtout que Silex a nativement une extension pour introduire le composant Validator.

En quelques commandes Git et deux-trois modifications de notre application, nous serons apte à valider les données qui vont transiter dans notre application :

git add submodule git://github.com/symfony/Validator.git vendor/Symfony/src/Symfony/Component/Validator

On édite maintenant notre application :

<?php
// portfolio.php
 
use Symfony\Component\Validator\Constraints;
// Others namespaces
 
// Bootstraping
 
// Registers Symfony Validator component extension
$app->register(new Silex\Extension\ValidatorExtension(), array(
   'validator.class_path'   => __DIR__ . '/../vendor/Symfony/src' 
));

portfolio.php @commit 87fa9cbb254983c655e3

Comme indiqué dans cette note, si vous souhaitez tester le bon fonctionnement de votre extension, vous devez l'appeler dans une de vos routes afin de voir si tout va bien :

<?php
// portfolio.php
 
$app->get('/', function() use ($app) {
    $app['validator']; // Appelle de l'extension pour voir si le class_path est bien configuré
    return $app['twig']->render('index.twig');
});

Allez tester sur votre index.

Tests sur la structure DOM du formulaire

Une fois que nous avons notre validateur de mis en place, on va créer notre formulaire de contact qui se voudra très simple :

  • Un champ « nom »
  • Un champ « email »
  • Une zone de texte
  • Un bouton envoyer

Comme nous l'avons fait au dessus, le but est de développer ce formulaire en TDD donc nous allons d'abord écrire le test qui va bien :

<?php
// WebAppTreeTest.php
 
public function testGetContact()
{
    $client = $this->createClient();
    $crawler = $client->request('GET', '/contact.html');
    $this->assertTrue($client->getResponse()->isOk());
 
    // DOM validation
    $this->assertEquals(1, $crawler->filter('h1')->count());
    $this->assertEquals(1, $crawler->filter('form[action][method="post"]')->count());
    $this->assertEquals(1, $crawler->filter('input[type="text"]')->count());
    $this->assertEquals(1, $crawler->filter('input[type="email"]')->count());
    $this->assertEquals(1, $crawler->filter('input[type="submit"]')->count());
    $this->assertEquals(1, $crawler->filter('textarea[rows][cols]')->count());
    $this->assertEquals(3, $crawler->filter('label')->count());
}

WebAppTreeTest.php @commit 861fe703fc5eccfb7011

Suite à ce test, PHPUnit ne devrait pas faillir à la première assertion mais à la seconde puisque la route /contact.html sera considérée comme un set. Il faut donc définir la route et créer le fichier .twig associé :

touch src/views/contact.twig
<?php
// portfolio.php
 
$app->get('/contact.html', function() use ($app) {
    return $app['twig']->render('contact.twig');
});

Remarque : il est super important de ne pas mettre cette route en dernier mais bien avant la route « set » sinon Silex ne rentrera jamais dedans !

Ok, maintenant PHPUnit devrait échouer sur les tests de DOM donc nous devons agrémenter le fichier contact.twig que nous venons de créer :

{# contact.twig #}
{% extends 'layout.twig' %}
 
{% block body %}
<h1>Drop me a note</h1>
<form method="post" action="/contact.html">
    <label>Name: <input type="text" name="name" value="{{ post.name }}" /></label>
    <label>Email: <input type="email" name="email" value="{{ post.email }}" /></label>
    <label>Message: <textarea cols="40" rows="10" name="message">{{ post.message }}</textarea></label>
    <input type="submit" value="Send" />
</form>
{% endblock %}

contact.twig @commit 861fe703fc5eccfb7011

Si vous avez bien tout fait, votre test de validation DOM du formulaire devrait désormais passer :

phpunit tests/WebAppTreeTest.php 
PHPUnit 3.5.14 by Sebastian Bergmann.

.....S.S.

Time: 0 seconds, Memory: 13.00Mb

OK, but incomplete or skipped tests!
Tests: 9, Assertions: 27, Skipped: 2.

Nous allons maintenant attaquer la partie vraiment intéressante du formulaire : mise en place des validateurs mais aussi le fait de pouvoir les tester.

Tests sur la validation du formulaire

Afin de faire cela proprement, nous allons séparer les tests spécifiques au formulaire dans un autre fichier de tests que nous nommerons ContactFormTest.php :

touch tests/ContactFormTest.php

Et voici la structure des tests sur laquelle nous allons bosser :

<?php
 
use Silex\WebTestCase;
use Symfony\Component\Validator\Constraints;
 
class ContactFormTest extends WebTestCase
{
    public function createApplication()
    {
        return require __DIR__ . '/../src/portfolio.php';
    }
 
    public function testFormHasLandingPage()
    {
        // @TODO
    }
 
    public function testSubmittedByCrawler()
    {
        // @TODO
    }
 
    public function testAllFieldEmptyValidatorMessages()
    {
        // @TODO
    }
}

Note : on ne travaillera ici qu'avec des 3 tests mais sachez qu'il y en a bien plus dans le fichier actuel.

Remarque : nous utilisons le namespace des contraintes de validation pour récupérer leurs classes et leurs attributs entre autres...

Comme vous pouvez le voir, le premier test est primordial puisqu'il ne repose pas sur la route que nous avons déjà écrite plus haut mais sur la route qui permettra d'afficher le résultat de la soumission du formulaire.
Si vous n'avez pas compris, je vous donne le test et vous comprendrez tout seul :

<?php
// ContactFormTest.php
 
public function testFormHasLandingPage()
{
    $client = $this->createClient();
    $client->request('POST', '/contact.html');
    $this->assertTrue($client->getResponse()->isOk());
}

PHPUnit échoue et c'est normal car il n'existe aucune route pour l'URI /contact.html avec la méthode POST d'HTTP.
A vous donc de la rajouter :

<?php
// portfolio.php
 
$app->post('/contact.html', function() use ($app) {
   $request = $app['request'];
   return $app['twig']->render('contact.twig', array(
       'post'           => array(
            'name'      => $request->get('name'),
            'email'     => $request->get('email'),
            'message'   => $request->get('message')
       )
   ));
});

Note : on ré-injecte les données soumises dans notre template dans un tableau associatif.

Le second test permettra de vérifier que le formulaire est bien formé avec pour but de le faire soumettre par le Client du BrowserKit. Ici encore, on ne test pas le contenu de la page, si il y a eu des erreurs ou pas mais simplement que le formulaire est opérationnel :

<?php
// ContactFormTest.php
 
public function testSubmittedByCrawler()
{
    $client = $this->createClient();
    $crawler = $client->request('GET', '/contact.html');
    $form = $crawler->selectButton('Send')->form();
    $client->submit($form);
    $this->assertTrue($client->getResponse()->isOk());
}

Remarque : la subtilité ici est de passer par le Crawler, récupérer le formulaire et dire au Client de cliquer sur « le bouton envoyer ».

Théoriquement, le test devrait passer par nature si vos tests de DOM du formulaire sont passés.
On attaques les choses sérieuses ?

On va faire ici du Symfony et du Twig donc on y va progressivement : on veut tester les messages d'erreurs renvoyés par le composant Validator de Symfony lorsque nos champs sont vides. On va donc tester la présence des messages au niveau du DOM mais aussi si les messages renvoyés sont ceux que nous attendons.

Notre test spécifie que le formulaire sera soumis sans aucune donnée or tous nos champs de saisie sont obligatoires mais pas que, nous voulons que :

  1. Aucun champ ne soit vide (NotBlankValidator)
  2. Un nom doit au moins faire 3 caractères de long (MinLengthValidator)
  3. Le format de l'adresse email doit être validé (EmailValidator)

Dans la logique de validation, seuls les messages de NotBlankValidator devraient remontés donc on ne va considérer que ceux-là :

<?php
// ContactFormTest.php
 
public function testAllFieldEmptyValidatorMessages()
{
    $client = $this->createClient();
    $crawler = $client->request('POST', '/contact.html');
    $not_blank_validator = new Constraints\NotBlank();
 
    $this->assertEquals(1, $crawler->filter('form')->count());
    $this->assertEquals(1, $crawler->filter('ul[class="warning"]')->count());
    $this->assertEquals(3, $crawler->filter('ul[class="warning"] > li')->count());
 
    $node_messages = $crawler->filter('ul[class="warning"] > li')->each(function($node, $i) {
        return $node->nodeValue;
    });
 
    foreach ($node_messages as $message) {
        $this->assertContains($not_blank_validator->message, $message);
    }
}

Dans ce test, on cherche donc à valider :

  • La présence du formulaire lorsque celui-ci est en erreur
  • La présence d'une liste de messages d'erreur
  • La présence de 3 messages d'erreur
  • La présence du message d'erreur par défaut dans les messages d'erreur renvoyés

Pour répondre positif à ce test, j'ai dû me plonger un peu dans la doc' de Symfony et ça m'a pris un certain temps mais voici le code qui saura faire taire PHPUnit (profitez-en bien hein) :

<?php
// portfolio.php
 
$app->post('/contact.html', function() use ($app) {
   $request = $app['request'];
   $validator = $app['validator'];
 
   $field_constraints = array(
        'name'      => array(
            new Constraints\NotBlank(),
            new Constraints\MinLength(3)
        ),
        'email'     => array(
            new Constraints\NotBlank(),
            new Constraints\Email()
        ),
        'message'   => array(
            new Constraints\NotBlank()
        )
   );
 
   // Loops on field contraints
   foreach ($field_constraints as $field => $constraints) {
       foreach ($constraints as $constraint) {
           // Gets contraint violations
           $violations = $validator->validateValue($request->get($field), $constraint);
 
           // If there are violations
           if ($violations->count()) {
               foreach ($violations as $violation) {
                   // Appends the violation message to the message stack
                   $violations_messages[$field] = $violation->getMessage();
               }
           }
       }
   }
 
   return $app['twig']->render('contact.twig', array(
       'post'           => array(
            'name'      => $request->get('name'),
            'email'     => $request->get('email'),
            'message'   => $request->get('message')
       ),
       'violations'     => $violations_messages
   ));
});

Je ne sais pas si c'est LA bonne manière de faire mais c'est ce que j'ai trouvé de mieux donc si vous avez des suggestions, je suis bien entendu preneur.
Concernant le fichier contact.twig, il a fallu ajouter un peu de code Twig pour afficher comme il se doit les messages d'erreur :

{# contact.twig #}
{% extends 'layout.twig' %}
 
{% block body %}
<h1>Drop me a note</h1>
{% if violations %}
    <ul class="warning">
        {% for field, message in violations %}
            <li>{{ field|title }} field raised: {{ message }}</li>
        {% endfor %}
    </ul>
{% endif %}
<form method="post" action="/contact.html">
    <label>Name: <input type="text" name="name" value="{{ post.name }}" /></label>
    <label>Email: <input type="email" name="email" value="{{ post.email }}" /></label>
    <label>Message: <textarea cols="40" rows="10" name="message">{{ post.message }}</textarea></label>
    <input type="submit" value="Send" />
</form>
{% endblock %}

Note de présentation : je bosse encore sur le fichier de langue Geshi afin d'avoir une belle coloration pour Twig

Avec ceci, vos tests devraient passer sans encombre... Par la suite (et c'est déjà commencé), il faudra rajouter des tests spécifiques sur le champ email ainsi que le champ name qui ont des validateurs plus spécifiques.

Vous pouvez récupérer la version actuelle de mon travail en faisant un checkout à ce commit si vous vous sentez un peu perdu (ce que je n'espère pas).

_Après ces quelques semaines sans article de ma part, la rédaction de ce nouveau petit chapitre m'a pris un petit bout de temps car "l'intégration Git" et la recherche approfondie dans la documentation demande un certain travail mais chut, ça faut pas le dire.

La prochaine fois, nous entamerons les tests unitaires sur le code métier de notre application et je pense qu'on fera aussi en même temps quelques tests DOM dans la foulée... Stay tunned on @JorisBerthelot._

Aller à l'étape suivante ›

‹ Aller à l'étape précédente


Joris Berthelot