Jean-David Daviet

Tester en isolation un template twig

Quand on utilise twig avec Symfony, en mode strict, il suffit d’accéder à une variable non définie pour qu’une exception soit levé. Quand on travaille sur des gros projets, il y a plus de risque qu’une typo s’insère dans le code, ou que l’on oublie de vérifier l’accès à une propriété. Pour ce genre d’erreur, on peut facilement mettre en place un test qui va vérifier que chaque modèle de page renvoie bien une réponse correct, sans erreur.

Cependant, récemment je voulais vérifier qu’un template twig renvoyait bien les bonnes données. Comme on utilise des macros dans nos templates twig, il y a de la logique qui se retrouve dans ces fichiers. Dans l’absolu je ne pense pas que ça soit une très bonne pratique, mais ça permet aux développeurs front d’avancer plus vite et d’être plus indépendant sur la déclaration des composant de l’application web.

Cette fonction que je souhaitais tester faisait donc appel à de la logique twig, ainsi qu’à une fonction PHP. J’ai donc trouvé que pour tester au mieux et plus facilement ce composant, je pouvais créer un « test unitaire », charger twig dans ce test et finalement créer un template qui ne contient que l’appel à la fonction concernée.

<?php

declare(strict_types=1);

namespace App\Tests;

use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Twig\Environment;

class ImageTest extends KernelTestCase
{
    private Environment $twig;

    protected function setUp(): void
    {
        $this->twig = self::getContainer()->get(Environment::class);
        $loader = $this->twig->getLoader();
        $loader->addPath('tests/templates');
    }

J’ai créé un template spécifique de test qui va seulement se charger d’appeler ma macro twig. Et je dis à Twig d’aller chercher le template dans un répertoire spécifique.

{% import "macros/medias.html.twig" as medias %}
{{ medias.picture(image) }}

Je crée un mock de mon image, car dans ce cas-là je voulais tester l’affichage d’un attribut de l’image en particulier. Ensuite, j’envoie ce mock dans mon fichier twig. C’est ce mock qui est utilisé dans l’appel medias.picture(image)

/**
* @dataProvider imageProvider
*/
public function testImageHasAltAttribute(string $expected, string $imageName, array $metas): void
{
    $mockFolder = $this->createMock(Folder::class);
    $mockFolder->method('getPath')->willReturn('mon-nom-de-dossier');

    $mockMedia = $this->createMock(Media::class);
    $mockMedia->method('getId')->willReturn(1);
    $mockMedia->method('getName')->willReturn($imageName);
    $mockMedia->method('getPath')->willReturn($mockFolder->getPath() . '/image.png');
    $mockMedia->method('getMetas')->willReturn($metas);

    $html = $this->twig->render('image.test.html.twig', ['image' => $mockMedia]);
    $crawler = new Crawler($html);

    self::assertNotSame(null, $crawler->filter('img')->attr('alt'), 'La balise  n\'a pas d\'attribut [alt]');
    self::assertNotEmpty($crawler->filter('img')->attr('alt'), 'La balise  a un attribut [alt] vide');
    self::assertEquals($expected, $crawler->filter('img')->attr('alt'));
}

Pour finir, je renseigne un provider avec mes différents cas d’usages

/**
* @return iterable<string, string, string, array{alt: string, title: string}>
*/
public function imageProvider(): iterable
{
    yield 'attribute alt empty and title empty' => ["Mon Nom D'image", "Mon Nom D'image", ['alt' => '', 'title' => '']];
    yield 'attribute alt empty and title defined' => ["Titre de l'image", "Mon Nom D'image", ['alt' => '', 'title' => 'Titre de l\'image']];
    yield 'attribute alt not empty and title defined' => ['Attribut alt', "Mon Nom D'image", ['alt' => 'Attribut alt', 'title' => 'Titre de l\'image']];
} 

Et j’ai maintenant un composant twig qui est utilisé dans plein d’endroit du code, mais qui est testé. Je serais dorénavant plus confiant sur la modification du code touchant le domaine de l’image, car les tests m’aideront à savoir si je n’ai rien cassé.

Ce test en particulier ne vérifie que la présence d’un attribut « alt“ sur la balise image, car c’est une demande explicitement formulée par le client, cependant on peut s’imaginer rajouter plus de teste pour tester les différents points critique de ces templates.

Je ne sais pas si c’est la meilleure façon de faire ou si c’est correct d’un point de vue organisation et découplage des tests, cependant ca me permet de tester facilement un template dans notre cas d’utilisation et j’espère que ca peut aussi donner de nouvelles idées pour tester dans le développement de sites utilisant twig.