Pour tester les controllers nous allons nous baser sur le système de WebTestCase de Symfony. Ce système va nous permettre d'envoyer une requête et de vérifier que la réponse est bien celle attendue.

Que doit-t-on tester ?

Un controller peut-être responsable de nombreuses opérations et on peut alors se demander quels éléments clés on va chercher à tester.
En premier lieu il faudra tester que la réponse est bien celle attendue (on comparera en général le statut, ou l'url de redirection). Par exemple si on questionne un controller qui doit récupérer un article et que cet article n'existe pas on attendra un statut 404. De la même manière si on essaie d'accéder à une page avec un accès limité on s'attendra à obtenir une redirection vers la page de connexion.

Il sera aussi possible d'écrire des tests basés sur la structure HTML du document renvoyé par notre controller. Cela peut être intéressant pour valider la présence de certains éléments liés au référencement (un h1 avec le bon titre par exemple), ou la présence d'éléments spécifiques comme un élément .alert ou un certain nombre d'éléments .product.

Enfin, il est aussi possible de faire appel à l'entity manager pour vérifier que le controller supprime ou ajoute convablement nos données à la base de données lors de la soumission d'un formulaire.

Tester un formulaire

Il y a deux techniques qui permettent de vérifier comme contrôleur répondre convenablement au traitement d'un formulaire.

La première technique consiste à se rendre sur la page précédente, utiliser le crawler pour récupérer le formulaire, le remplir et ensuite le soumettre. Cette technique a l'avantage 2 testés en partie l'interface l'utilisateur (en revanche le crawler est relativement naïf et il ne sera pas possible de tester un formulaire qui fait intervenir les modifications JavaScript).

public function testLoginWithBadCredentials()
{
    $client = static::createClient();
    $crawler = $client->request('GET', '/login');
    $form = $crawler->selectButton('Se connecter')->form([
        'email' => 'john@doe.fr',
        'password' => 'fakepassword'
    ]);
    $client->submit($form);
    $this->assertResponseRedirects('/login');
    $client->followRedirect();
    $this->assertSelectorExists('.alert.alert-danger');
}

La seconde approche consiste à soumettre les données d'un formulaire directement à notre controller. Le principal inconvénient de cette approche et qu'il faudra souvent générer un token CSRF afin de ne pas obtenir une erreur.

public function testSuccessfullLogin () 
{
    $this->loadFixtureFiles([__DIR__ . '/users.yaml']);
    $client = static::createClient();
    $csrfToken = $client->getContainer()->get('security.csrf.token_manager')->getToken('authenticate');
    $client->request('POST', '/login', [
        '_csrf_token' => $csrfToken,
        'email' => 'john@doe.fr',
        'password' => '000000'
    ]);
    $this->assertResponseRedirects('/auth');
}

Tester les emails

Pour tester que notre application envoie convenablement des emails il est possible de passer par le profiler. On commencera par l'activer avant de faire notre requête et nous vérifierons ensuite que l'email est bien dans le mailCollector.

public function testMailSendEmail () 
{
    $client = static::createClient();
    $client->enableProfiler();
    $client->request('GET', '/mail');
    $mailCollector = $client->getProfile()->getCollector('swiftmailer');
    $this->assertEquals(1, $mailCollector->getMessageCount());
    /** @var \Swift_Message[] $messages */
    $messages = $mailCollector->getMessages();
    $this->assertEquals($messages[0]->getTo(), ['contact@doe.fr' => null]);
}

Les pages nécessitant une authentification

Certaines pages de notre application se trouve derrière le pare-feu du composant security et nécessite que l'utilisateur soit authentifié pour y accéder. Il faudra donc identifier le client avant de pouvoir lancer notre requête. Il est possible de modifier la configuration du projet pour l'environnement de test afin d'avoir un système d'authentification simplifié. Même si cette approche fonctionne, dans les faits, elle ne permet pas forcément de convenablement tester le comportement finale de notre application et on préférera en général se baser sur le même système d'authentification que notre environnement de production.

Dans ce cas-là, il est possible de créer un cookie au niveau de notre client qui contiendra les informations de session de notre utilisateur. On créera un trait pour se simplifier la tâche.

<?php
namespace App\Tests;

use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Component\BrowserKit\Cookie;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;

trait NeedLogin
{

    /**
     * Connecter un utilisateur sur le client en se basant sur le système de Cookie
     **/
    public function login (KernelBrowser $client, User $user)
    {
        $session = $client->getContainer()->get('session');
        $token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
        $session->set('_security_main', serialize($token));
        $session->save();

        $cookie = new Cookie($session->getName(), $session->getId());
        $client->getCookieJar()->set($cookie);
    }

}