Aller au contenu

CI - Tests unitaires đŸ§Ș⚓

L’exĂ©cution de tests unitaires et d’intĂ©grations est au coeur de la dĂ©marche DevOps et d’intĂ©gration continue. C’est eux qui vont permettre de dĂ©tecter les anomalies dans les dĂ©veloppements rĂ©alisĂ©s. Github Actions, si un ou plusieurs tests sont en erreur, va fournir les rapports dĂ©taillĂ©s permettant d’identifier l’origine de l’erreur, et va signaler que la tĂąche est en erreur. Cela peut par exemple empĂȘcher un merge de se rĂ©aliser dans Github.

- name: Run tests with coverage
  env: 
    XDEBUG_MODE: coverage 
    run: php artisan test --coverage --min=80

🎯 Objectifs pĂ©dagogiques

  • Comprendre la structure des tests Unit et Feature dans Laravel.
  • Écrire, exĂ©cuter et interprĂ©ter des tests automatisĂ©s.
  • IntĂ©grer les tests dans une pipeline d’intĂ©gration continue (CI).
  • Mesurer la couverture de code et fixer un seuil minimal de qualitĂ©.

1. Initialisation de l’environnement de test đŸ—ïžâš“ïžŽ

Crée un environnement dédié aux tests.

cp .env .env.testing
php artisan key:generate

▶ Configure .env.testing ⚙

D'abord la base de données de Test :

CREATE DATABASE todo_test;
CREATE USER 'laravel_test'@'localhost' IDENTIFIED BY 'secret';
GRANT ALL PRIVILEGES ON todo_test.* TO 'laravel_test'@'localhost';
FLUSH PRIVILEGES;

puis la configuration

DB_CONNECTION=mysql 
DB_HOST=127.0.0.1 
DB_PORT=3306 
DB_DATABASE=todo_test 
DB_USERNAME=laravel_test 
DB_PASSWORD=secret
Fix problĂšm đŸȘČ sqlite

Par dĂ©faut, Laravel utilise SQLite pour l’exĂ©cution des tests. Cette base, entiĂšrement en mĂ©moire, est trĂšs performante et particuliĂšrement adaptĂ©e aux tests unitaires. Cependant, SQLite gĂšre mal certaines fonctionnalitĂ©s avancĂ©es, notamment les clĂ©s primaires composites que nous avons mises en place dans nos migrations.

Pour garantir un comportement cohĂ©rent entre l'environnement de dĂ©veloppement, l'environnement de test et l'environnement de production, nous faisons donc le choix d’utiliser MySQL Ă©galement pour les tests.

Dans la continuitĂ©, nous provisionnerons un service MySQL via Docker au sein de GitHub Actions afin d’exĂ©cuter automatiquement la suite de tests dans un environnement reproductible et compatible avec nos contraintes de schĂ©ma.

dans le fichier phpunit.xml Ă  la racine du projet text <!-- <server name="DB_CONNECTION" value="sqlite"/> <server name="DB_DATABASE" value=":memory:"/> --> <server name="DB_CONNECTION" value="mysql"/> <server name="DB_DATABASE" value="todo_test"/> <server name="DB_USERNAME" value="laravel_test"/> <server name="DB_PASSWORD" value="secret"/>

2. Premier test “Smoke test” đŸ§©âš“ïžŽ

CrĂ©e un test pour vĂ©rifier que la page d’accueil est accessible.

php artisan make:test AccueilTest

Cela créé un nouveau répertoire tests, modifions à présent tests/Feature/AccueilTest.php :

<?php
public function test_invite_est_redirige_depuis_accueil_vers_login()
{
    $response = $this->get('/');
    $response->assertRedirect(route('login'));
}

public function test_utilisateur_auth_peut_acceder_a_l_accueil()
{
    $user = User::factory()->create();

    $response = $this->actingAs($user)
                     ->followingRedirects()
                     ->get('/');

    $response->assertOk();
    // On vérifiera qu'on voit bien le texte "Ma Todo List" dans la page
    $response->assertSee('Ma Todo List');
}
  • assertRedirect(route('login')) montre le comportement pour un invitĂ©.
  • actingAs($user) + followingRedirects() illustre le scĂ©nario d’un utilisateur authentifiĂ©.
  • L’assertion porte sur un texte rĂ©el de la vue, pas sur le nom de la route.

🏃 Lançons les tests :

php artisan test
Fix problĂ©m đŸȘČ
  • Mettre en commentaire le contenu de exampleTest.php
  • RĂ©parer le test d'authentification

bash FAILED Tests\Feature\Auth\AuthenticationTest > users can authenticate using the login screen Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ -'http://localhost/dashboard' +'http://localhost' +++ Actual @@ @@ -'http://localhost/dashboard' +'http://localhost' @@ @@ -'http://localhost/dashboard' +'http://localhost' -'http://localhost/dashboard' +'http://localhost' +'http://localhost'

résultats

```bash (base) PS C:\wamp64\www\todo2026> php artisan test

PASS Tests\Unit\ExampleTest ✓ that true is true 0.02s

PASS Tests\Feature\AccueilTest ✓ invite est redirige depuis accueil vers login 0.63s
✓ utilisateur auth peut acceder a l accueil 0.58s

PASS Tests\Feature\Auth\AuthenticationTest ✓ login screen can be rendered 3.22s
✓ users can authenticate using the login screen 0.17s
✓ users can not authenticate with invalid password 0.29s
✓ users can logout 0.10s

PASS Tests\Feature\Auth\EmailVerificationTest ✓ email verification screen can be rendered 0.14s
✓ email can be verified 0.08s
✓ email is not verified with invalid hash 0.10s

PASS Tests\Feature\Auth\PasswordConfirmationTest ✓ confirm password screen can be rendered 0.12s
✓ password can be confirmed 0.07s
✓ password is not confirmed with invalid password 0.30s

PASS Tests\Feature\Auth\PasswordResetTest ✓ reset password link screen can be rendered 0.07s
✓ reset password link can be requested 0.27s
✓ reset password screen can be rendered 0.38s
✓ password can be reset with valid token 0.33s

PASS Tests\Feature\Auth\PasswordUpdateTest ✓ password can be updated 0.08s
✓ correct password must be provided to update password 0.08s

PASS Tests\Feature\Auth\RegistrationTest ✓ registration screen can be rendered 0.07s
✓ new users can register 0.07s

PASS Tests\Feature\ProfileTest ✓ profile page is displayed 0.19s
✓ profile information can be updated 0.07s
✓ email verification status is unchanged when the email address is unchanged 0.08s
✓ user can delete their account 0.08s
✓ correct password must be provided to delete account 0.10s

Tests: 26 passed (64 assertions) Duration: 8.40s ```

3. Les factories Eloquent đŸ­âš“ïžŽ

đŸ§© Qu’est-ce qu’une Factory Laravel ?

Une factory est un outil fourni par Laravel pour générer automatiquement des données de test réalistes.

Elle permet de créer facilement :

  • des objets Eloquent cohĂ©rents (User, Todo, Liste, etc.),
  • avec des valeurs alĂ©atoires mais crĂ©dibles (texte, dates, boolĂ©ens
),
  • et surtout sans Ă©crire manuellement chaque champ.

Les factories sont essentielles pour les tests, car elles permettent :

  • de simplifier la crĂ©ation d’entrĂ©es en base,
  • d’éviter de dupliquer du code,
  • d’isoler les tests (chaque test crĂ©e ses propres donnĂ©es),

et d’obtenir des tests prĂ©cis, reproductibles et rapides.

Les fichiers de factories se trouvent dans le dossier database/factories/

note : Laravel fournit déjà une factory pour User : UserFactory.php.

3.1. VĂ©rifier la factory User đŸ§‘â€đŸ’»âš“ïžŽ

Ouvre le fichier suivant : database/factories/UserFactory.php

Tu dois y trouver une méthode definition() qui ressemble à ceci :

<?php
public function definition(): array
    {
        return [
            'name' => fake()->name(),
            'email' => fake()->unique()->safeEmail(),
            'email_verified_at' => now(),
            'password' => static::$password ??= Hash::make('password'),
            'remember_token' => Str::random(10),
        ];
    }

L’important pour nous est :

👉 User::factory()->create() permet de crĂ©er facilement un utilisateur de test valide.

3.2. CrĂ©er une factory pour Listes đŸ“‚âš“ïžŽ

On veut pouvoir gĂ©nĂ©rer des listes de Todos pour les tests (ex. “Maison”, “Travail”, etc.).

Crée la factory : php artisan make:factory ListesFactory --model=Listes

Puis complĂšte database/factories/ListesFactory.php :

<?php
use App\Models\Listes;
use Illuminate\Database\Eloquent\Factories\Factory;

class ListesFactory extends Factory
{
   protected $model = Listes::class;

    public function definition(): array
    {
        return [
            'titre' => fake()->words(2, true), // par ex. "Maison", "Bureau perso"
        ];
    }

}

💡 Explication ListeFactory

Ici, on utilise fake()->words(2, true) pour générer un nom de liste avec 2 mots.
À partir de lĂ , un simple Listes::factory()->create() dans un test insĂ©rera une ligne dans la table listes avec un champ titre cohĂ©rent.

A faire

✅ CrĂ©er une factory pour Todos (sans relations)

```php <?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory; use App\Models\Todos; use App\Models\Listes; use App\Models\User;

/** * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Todos> */ class TodosFactory extends Factory { protected $model = Todos::class;

📋 Texte
public function definition(): array
{
    return [
        'texte'     => fake()->sentence(3),
        'termine'   => false,
        'important' => false,
        'date_fin'  => null,
        'listes_id' => Listes::factory(), // crée une Liste associée
        'user_id'   => User::factory(),  // crée un User associé
    ];
}

} ```

Avec cette factory, on peut déjà écrire :

<?php
$todos = Todos::factory()->create();

Laravel créera un enregistrement dans la table todos avec :

  • un texte rĂ©aliste,
  • termine = false,
  • important = false,
  • les FK listes_id et user_id Ă  null (non associĂ©es pour l’instant).

C’est suffisant pour des premiers tests sur les attributs du modùle Todo.

3.3. VĂ©rifier le bon fonctionnement des factories đŸŽąâš“ïžŽ

Tinker est une console interactive fournie par Laravel, basĂ©e sur PsySH, qui permet d’exĂ©cuter du code PHP en direct au sein de ton application.

C’est un outil extrĂȘmement pratique pour :

  • tester rapidement des modĂšles Eloquent,
  • manipuler la base de donnĂ©es depuis le shell,
  • vĂ©rifier le fonctionnement d’une factory,
  • expĂ©rimenter un morceau de logique mĂ©tier,
  • dĂ©boguer un comportement sans Ă©crire de route ni de test.

▶ Ouvrir Tinker

Dans ton terminal (Ă  la racine du projet) : php artisan tinker

Tu obtiens alors une console interactive :

Psy Shell v0.11.8 (PHP 8.2 
)
>>>

Tout ce que tu écris ici est exécuté dans le contexte de ton application Laravel.

✔ Tester une factory

> App\Models\Listes::factory()->create();
= App\Models\Listes {#6211
    titre: "eos ut",
    updated_at: "2025-12-06 13:09:32",
    created_at: "2025-12-06 13:09:32",
    id: 4,
  }

Résultat : un nouvel enregistrement todos est inséré dans la base de développement.

Fix problĂšme đŸȘČ
  • penser Ă  ajouter use HasFactory; dans le model de Todos
  • Vider le cache Laravel

bash php artisan optimize:clear composer dump-autoload

3.4. Todo liĂ© Ă  une Liste et Ă  un User đŸ”—âš“ïžŽ

Pour des tests plus complets (relations Eloquent), on peut demander à la factory Todo de créer automatiquement une Liste et un User associés.

code TodoFactory

```php <?php use App\Models\Todos; use App\Models\Listes; use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory;

class TodosFactory extends Factory { protected $model = Todos::class;

📋 Texte
public function definition(): array
{
    return [
        'texte'     => fake()->sentence(3),
        'termine'   => false,
        'important' => false,
        'date_fin'  => null,
        'listes_id' => Listes::factory(), // crée une Liste associée
        'user_id'   => User::factory(),  // crée un User associé
    ];
}

}

```

En configurant listes_id et user_id avec Liste::factory() et User::factory(), on obtient un comportement trĂšs puissant :

<?php
$todo = Todos::factory()->create();

réalise en réalité :

  1. crĂ©ation d’un User de test,
  2. crĂ©ation d’une Listes de test,
  3. création du Todos lié à ces deux enregistrements.

Cela simplifie énormément les tests qui portent à la fois sur Todos et sur ses relations (ex. todos->listes, todos->user).

✔ CrĂ©er un utilisateur

> App\Models\User::factory()->create();
= App\Models\User {#6632
    name: "Diane Lambert",
    email: "pauline27@example.net",
    email_verified_at: "2025-12-06 13:14:50",
    #password: "$2y$12$mynJ8kscphRfl1KCdRRVR.XS8hKU0dxzQvaBkTWWc8P9TkgonEgEq",
    #remember_token: "nNiqZwe2AN",
    updated_at: "2025-12-06 13:14:51",
    created_at: "2025-12-06 13:14:51",
    id: 3,
  }

✔ Tester la factory Todos

> App\Models\Todos::factory()->create();
= App\Models\Todos {#5520
    texte: "Ut rerum praesentium fugit.",
    termine: false,
    important: false,
    date_fin: null,
    listes_id: 5,
    user_id: 4,
    updated_at: "2025-12-06 13:22:15",
    created_at: "2025-12-06 13:22:15",
    id: 9,
  }

✔ Lister les enregistrements existants : App\Models\Todos::all();

4. Tests Unitaires : modĂšle Eloquent đŸ§±âš“ïžŽ

Dans cette Ă©tape, nous allons vĂ©rifier le comportement du modĂšle Todos Ă  l’aide de tests unitaires :

  • relations Todos → Listes et Todos → User,
  • valeurs par dĂ©faut de certains attributs (termine, important, etc.).

L’objectif est de valider le modùle sans passer par les routes ni les vues.

4.1 CrĂ©ation du test de modĂšle đŸ§Ș⚓

GénÚre un test unitaire dédié au modÚle Todos :

php artisan make:test TodosModelTest --unit

Cela crée le fichier tests/Unit/TodosModelTest.php

Modifier l’en-tĂȘte du fichier pour qu’il Ă©tende bien la classe de test Laravel (et pas le TestCase brut de PHPUnit), afin de pouvoir utiliser les factories et la base de donnĂ©es de test.

<?php

namespace Tests\Unit;

use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Models\Todos;
use App\Models\Listes;
use App\Models\User;

class TodosModelTest extends TestCase
{
    use RefreshDatabase;

    // Les tests viendront ici
}
💡 Pourquoi utiliser Tests\TestCase et RefreshDatabase ?
  • Tests\TestCasecharge le contexte Laravel complet (config, Eloquent, etc.) dans les tests, mĂȘme dans tests/Unit.
  • RefreshDatabase relance les migrations pour chaque test afin de garantir une base de test propre (pas de pollution entre tests).

4.2 Tester la relation Todos → Listes đŸ”—âš“ïžŽ

Ton modÚle Todos déclare une relation :

<?php
public function listes(): BelongsTo
{
    return $this->belongsTo(Listes::class)->withDefault();
}

Nous allons vérifier que cette relation fonctionne bien.

Dans TodosModelTest :

<?php
public function test_un_todos_appartient_a_une_liste()
{
    // Arrange : création d'une liste
    $liste = Listes::factory()->create();

    // Act : création d'un Todos lié à cette liste
    $todo = Todos::factory()->for($liste, 'listes')->create();

    // Assert : la relation renvoie bien la bonne liste
    $this->assertTrue($todo->listes->is($liste));
}
💡 DĂ©tail sur for($liste, 'listes')
  • Listes::factory()->create() crĂ©e une ligne dans la table listes.
  • Todos::factory()->for($liste, 'listes')->create() demande Ă  Laravel de remplir la clĂ© Ă©trangĂšre correspondant Ă  la relation listes dans le modĂšle Todos.
  • L’assertion is() vĂ©rifie que $todo->listes et $liste reprĂ©sentent le mĂȘme enregistrement.

🏃 Relancer les tests : php artisan test

A faire

Tester la relation Todos → User đŸ‘€

```php <?php public function test_un_todos_appartient_a_un_utilisateur() { // Arrange : création d'un utilisateur $user = User::factory()->create();

📋 Texte
// Act : création d'un Todos lié à cet utilisateur
$todo = Todos::factory()->for($user, 'user')->create();

// Assert : la relation renvoie bien le bon utilisateur
$this->assertTrue($todo->user->is($user));

} ```

💡 Pourquoi tester les relations ?

Ces tests garantissent que :

  • les clĂ©s Ă©trangĂšres (listes_id, user_id) sont correctement utilisĂ©es,
  • les mĂ©thodes listes() et user() renvoient bien les modĂšles attendus.

En cas de renommage de colonne, de modĂšle ou de relation, ces tests serviront de garde-fous.

A faire

Tester les valeurs par dĂ©faut (termine, important) ✅

```php <?php public function test_un_todos_est_non_termine_et_non_important_par_defaut() { // Act : création d'un Todos sans préciser termine/important $todo = Todos::factory()->create();

📋 Texte
// Assert : les valeurs par défaut sont respectées
$this->assertFalse($todo->termine);
$this->assertFalse($todo->important);

}
```

5.Tests de validation đŸ§źâš“ïžŽ

Nous allons maintenant Ă©crire des tests de validation pour le formulaire d’ajout d’un Todo.

🎯 Objectifs"

  • VĂ©rifier que certains champs sont obligatoires (texte notamment).
  • VĂ©rifier que certains champs respectent des contraintes (longueur minimale, format de date
).
  • VĂ©rifier qu’un Todo valide est bien créé en base.

5.1 PrĂ©parer un test dĂ©diĂ© đŸ“„âš“ïžŽ

Crée un test de type Feature php artisan make:test TodosValidationTest

Cela créé un fichier dans tests/Feature/TodosValidationTest.php, modifier le code ainsi :

<?php

namespace Tests\Feature;

use App\Models\User;
use App\Models\Listes;
use App\Models\Todos;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class TodosValidationTest extends TestCase
{
    use RefreshDatabase;

    /**
     * Méthode utilitaire pour poster un Todo avec des données par défaut
     */
    private function postTodo(array $overrides = [])
    {
        $user = User::factory()->create();
        $this->actingAs($user);

        $liste = Listes::factory()->create();

        $data = array_merge([
            'texte'      => 'Acheter du café',
            'date_fin'   => now()->addDay()->format('Y-m-d\TH:i'),
            'priority'   => 0,              // bouton radio importance
            'categories' => [],             // tableau de catégories (checkbox)
            'liste'      => $liste->id,     // correspond Ă  $request->input('liste')
        ], $overrides);

        return $this->post('/action/add', $data);
    }
}
💡 Pourquoi une mĂ©thode postTodo() ?

Cette méthode permet de :

  • centralise la construction d’un jeu de donnĂ©es valide,
  • permet d’écrire des tests plus courts en ne modifiant que les champs Ă  tester,
  • garantit que tous les autres champs restent cohĂ©rents (liste existante, user connectĂ©, etc.).

5.2 texte est obligatoire ✏⚓

Premier cas : le champ texte doit ĂȘtre obligatoire.

<?php
public function test_texte_est_obligatoire()
{
    $response = $this->postTodo(['texte' => '']);

    $response->assertSessionHasErrors('texte');
    $this->assertDatabaseCount('todos', 0);
}

question "💡 Comportement attendu cĂŽtĂ© contrĂŽleur ?"

Dans ton contrĂŽleur qui traite /action/add, tu dois avoir quelque chose comme :

```php validate([ 'texte' => ['required', 'string'], // autres rĂšgles
 ]); ``` Le test vĂ©rifie que : - si `texte` est vide, Laravel renvoie une **erreur de validation** sur ce champ, - aucun `Todos` n’est créé en base. mais

FAIL  Tests\Feature\TodosValidationTest
  ⚯ texte est obligatoire                                                                                                       0.11s  
  ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────  
   FAILED  Tests\Feature\TodosValidationTest > texte est obligatoire
  Session is missing expected key [errors].
Failed asserting that false is true.

  at tests\Feature\TodosValidationTest.php:40
     36▕     public function test_texte_est_obligatoire()
     37▕     {
     38▕         $response = $this->postTodo(['texte' => '']);
     39▕
  ➜  40▕         $response->assertSessionHasErrors('texte');
     41▕         $this->assertDatabaseCount('todos', 0);
     42▕     }
     43▕ }
     44▕
Il faut **adapter** le test au contrĂŽleur actuel ! Le contrĂŽleur, en cas de texte vide, ne renvoie pas d’errors bag, mais : + dĂ©clenche une ValidationException, + la catch renvoie un ``redirect()->route('todo.liste')->with('message', '...')``. Donc le test doit vĂ©rifier : - la redirection vers la route, - la prĂ©sence du message en session, - l’absence de nouvelle ligne dans todos.
<?php
public function test_texte_est_obligatoire()
{
    $response = $this->postTodo(['texte' => '']);
    $response->assertRedirect(route('todo.liste'));
    $response->assertSessionHas('message', "Veuillez saisir un ToDo d'une longueur max de 255 caractĂšres");
    $this->assertDatabaseCount('todos', 0);
}
et ca passe beaucoup mieux ...
   PASS  Tests\Feature\TodosValidationTest
  ✓ texte est obligatoire                                                                                                       0.11s  

  Tests:    28 passed (69 assertions)
  Duration: 6.48s
đŸš© Mais quelle est la bonne pratique ? !!! question "A faire : longueur minimale" === "EnoncĂ©" Actuellement, la rĂšgle est ``'texte' => 'required|string|max:255',`` ▶ La rĂšgle doit Ă  prĂ©sent ĂȘtre d'avoir une contrainte supplĂ©mentaire, une longueur minimale de 3 caractĂšres - ImplĂ©menter cette rĂšgle - Faites en sorte qu'elle soit bien prise en compte dans votre interface - Tester cette rĂšgle dans les features de tests. === "Solution" dans le controleur : ```php validate([ 'texte' => 'required|string|min:3|max:255', ]); ``` dans les tests : ```php postTodo(['texte' => 'ab']); // 2 caractĂšres $response->assertSessionHasErrors('texte'); $this->assertDatabaseCount('todos', 0); } ``` !!! tip "ExĂ©cution ciblĂ©e des tests de validation ▶" Pour exĂ©cuter uniquement cette classe de tests : ```bash php artisan test --filter=TodosValidationTest ``` ### 5.3 Cas nominal ➕✔ Il est important de tester aussi le scĂ©nario heureux 😃 :
quand toutes les donnĂ©es sont **valides**, le Todo doit ĂȘtre créé.
<?php
public function test_un_todos_valide_est_cree()
{
    $response = $this->postTodo(); // toutes les valeurs par défaut sont valides

    $response->assertSessionDoesntHaveErrors();
    $response->assertRedirect(); // ou ->assertRedirect('/'); selon ton contrĂŽleur

    $this->assertDatabaseCount('todos', 1);

    $this->assertDatabaseHas('todos', [
        'texte'   => 'Acheter du café',
        'termine' => 0,
        'important' => 0,
    ]);
}
!!! question "💡 Pourquoi ce test est essentiel ?" - Il vĂ©rifie que la crĂ©ation fonctionne en cas de donnĂ©es valides. - Il sert de rĂ©fĂ©rence pour les autres tests de validation : ils ne doivent empĂȘcher que les donnĂ©es invalides. - En cas de modification ultĂ©rieure du contrĂŽleur, ce test permet de dĂ©tecter une rĂ©gression sur le CAS NOMINAL. !!! info "Available assertions 📕" Il existe de nombreuses assertions diffĂ©rentes. Elles sont disponible dans la [documentation](https://laravel.com/docs/12.x/database-testing#available-assertions) de Laravel. ## 6. Gestion de la base de donnĂ©es pendant les tests 🧰 Jusqu’ici, nous avons Ă©crit des tests qui crĂ©ent des utilisateurs, des listes et des todos Ă  l’aide des **factories**. Il est maintenant important de bien comprendre comment Laravel gĂšre la **base de donnĂ©es** pendant les tests. !!! info "🎯 **Objectifs**" - Comprendre comment **isoler** chaque test (pas de pollution entre tests). - Savoir sur **quelle base** s’exĂ©cutent les tests (`.env.testing`). - Utiliser les assertions `assertDatabaseHas`, `assertDatabaseMissing`, `assertDatabaseCount`. ### 6.1 Le trait `RefreshDatabase` 🔄 [documentation Laravel sur database-testing](https://laravel.com/docs/12.x/database-testing){target="_blank"} Laravel fournit le trait ``use Illuminate\Foundation\Testing\RefreshDatabase;`` Ce trait, utilisĂ© dans une classe de test, garantit que : - les migrations sont exĂ©cutĂ©es pour la base de test, - la base est remise Ă  zĂ©ro entre les tests (soit par migrate:fresh, soit par transactions selon le driver). **Exemple** (dĂ©jĂ  utilisĂ© dans tes tests) :
<?php
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class TodosValidationTest extends TestCase
{
    use RefreshDatabase;

    // ...
}
??? question "💡 Que fait RefreshDatabase exactement ?" - Avant le premier test, Laravel exĂ©cute ``php artisan migrate`` sur la connexion de test. - Entre les tests, il remet la base dans un Ă©tat propre (``rollback`` ou ``migrate:fresh`` selon le driver). - Cela permet de s’assurer que chaque test est indĂ©pendant des autres : aucun enregistrement laissĂ© par un test ne doit influencer un autre test. ### 6.2 Quelle base est utilisĂ©e pour les tests ? đŸ—„ïž Laravel choisit la base de donnĂ©es de test via Le fichier ``.env.testing`` ou Ă©ventuellement des variables dans ``phpunit.xml`` Dans ton cas, tu as configurĂ© une base MySQL dĂ©diĂ©e, par exemple : dans ``.env.testing``
APP_ENV=testing
APP_DEBUG=true

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=todo_test
DB_USERNAME=laravel_test
DB_PASSWORD=secret
Pendant les tests, on ne touche jamais Ă  la base de dĂ©veloppement (``todo2025``) : tout se passe dans la base ``todo_test``. ??? question "💡 Comment vĂ©rifier que Laravel utilise bien la bonne base ?" - Tu peux temporairement ajouter un test “de debug” : ```php assertSame('mysql', config('database.default')); $this->assertSame('todo_test', config('database.connections.mysql.database')); } ``` - Puis le supprimer une fois que tu es sĂ»re de ta configuration. ### 6.3 ExĂ©cuter les tests ▶ En gĂ©nĂ©ral, tu n’as pas besoin de te soucier manuellement des migrations : ``RefreshDatabase`` s’en charge. Mais si tu modifies les migrations ou la structure de la base, tu peux forcer un reset complet :
php artisan migrate:fresh --env=testing
php artisan test
??? question "💡 Quand utiliser ``migrate:fresh --env=testing`` ?" - Quand tu as modifiĂ© une migration existante (ajout/suppression de colonnes). - Quand tu veux repartir d’une base de test complĂštement propre. - À Ă©viter en production, bien sĂ»r : cela supprime toutes les tables avant de les recrĂ©er. ## 7. Tests d’authentification 🔐 Laravel gĂ©nĂšre automatiquement une sĂ©rie complĂšte de **tests d’authentification et de sĂ©curitĂ©**, via Breeze ou Jetstream. Ces tests couvrent dĂ©jĂ  toutes les fonctionnalitĂ©s essentielles : - affichage des formulaires de connexion / inscription, - validation des identifiants, - impossibilitĂ© de se connecter avec un mot de passe incorrect, - confirmation de mot de passe, - mise Ă  jour du mot de passe, - rĂ©initialisation de mot de passe, - vĂ©rification d’e-mail, - dĂ©connexion de l’utilisateur. Ces tests se trouvent dans ``tests/Feature/Auth/`` ??? question "Pourquoi conserver ces tests ? đŸ›Ąïž" Ces tests assurent automatiquement : - que l’authentification reste fonctionnelle aprĂšs une mise Ă  jour, - que les Ă©tudiants ne cassent pas la sĂ©curitĂ© en modifiant une vue ou une route, - que chaque changement sur les routes ou les middlewares continue de respecter les rĂšgles Laravel (auth, verified, etc.). - Ils constituent une base solide pour Ă©viter les rĂ©gressions pendant le dĂ©veloppement. ### 7.1 Tests d’accĂšs aux routes protĂ©gĂ©es (middleware auth) 🔒 Il peut ĂȘtre utile d’ajouter un seul test d'exemple dans ton TP pour illustrer comment vĂ©rifier qu’une route est bien protĂ©gĂ©e par auth.
<?php
public function test_invite_ne_peut_pas_acceder_aux_todos()
{
    $response = $this->get('/todos');
    $response->assertRedirect(route('login'));
}
### 7.2 Tests liĂ©s Ă  l'autorisation (Policies) 🧭 Lorsque tu ajoutes une policy pour restreindre l’accĂšs aux Todos : - un utilisateur ne peut modifier que ses propres Todos, - un utilisateur ne peut supprimer que ses Todos, etc., tu peux tester ces rĂšgles simplement avec :
<?php
public function test_un_utilisateur_ne_peut_pas_modifier_le_todo_d_un_autre()
{
    $a = User::factory()->create();
    $b = User::factory()->create();
    $todo = Todos::factory()->for($a, 'user')->create();

    $response = $this->actingAs($b)->post("/todos/{$todo->id}/edit", [
        'texte' => 'H4ck',
    ]);

    $response->assertForbidden();
}
Laravel renverra automatiquement **403** Forbidden si la policy refuse l’accĂšs. ## 8. IntĂ©gration dans GitHub Actions ⚙ 🎯 **Objectif** : exĂ©cuter automatiquement les tests Laravel Ă  chaque **push** ou **pull request** sur GitHub, avec un **seuil de couverture** minimal. 🔑 **IdĂ©e clĂ©** : - En **local**, tu peux utiliser MySQL (`todo_test`). - En **CI GitHub Actions**, on va utiliser **SQLite en mĂ©moire** (plus simple, plus rapide), en Ă©crivant un `.env.testing` spĂ©cifique dans le workflow. - On active la **couverture de tests** pour mesurer la part du code rĂ©ellement exĂ©cutĂ©e par les tests, et on impose un seuil minimal (**80 %**). ### 8.1. CrĂ©er le workflow `tests.yml` đŸ§Ÿ Dans ton projet, crĂ©e un fichier : `.github/workflows/tests.yml` Avec le contenu suivant :
name: tests

on:
  push:
  pull_request:

jobs:
  laravel-tests:
    runs-on: ubuntu-latest

    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_ROOT_PASSWORD: root
          MYSQL_DATABASE: todo_test
          MYSQL_USER: laravel_test
          MYSQL_PASSWORD: secret
        ports:
          - 3306:3306
        options: >-
          --health-cmd="mysqladmin ping -h 127.0.0.1 -uroot -proot"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=10

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'
          extensions: mbstring, dom, pdo_mysql
          coverage: xdebug

      - name: Install PHP dependencies
        run: composer install --no-interaction --prefer-dist

      # âŹ‡ïž Partie frontend / Vite : crĂ©ation du manifest.json
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install frontend dependencies
        run: npm ci

      - name: Build assets
        run: npm run build

      # âŹ‡ïž On aligne l'environnement de la CI sur ton env de test local
      - name: Prepare environment file
        run: cp .env.testing .env

      - name: Run migrations
        run: php artisan migrate --force

      - name: Run tests with coverage
        env:
          XDEBUG_MODE: coverage
        run: php artisan test --coverage --min=80
### 8.2. Mesurer la couverture en local 📊 !!! info "DĂ©finition 💡" La couverture de code est une mĂ©trique qui peut vous permettre de comprendre la **part** testĂ©e de votre source. Cette mĂ©trique est trĂšs utile, car elle peut vous aider Ă  Ă©valuer la qualitĂ© de votre suite de tests. phpunit regarde quelles lignes ont Ă©tĂ© "utilisĂ©es" dans le code quand on a lancĂ© les tests unitaires. Ensuite en fonction du nombre de lignes utilisĂ©es par rapport au nombre de lignes total du fichier, phpunit calcule un taux de couverture pour le fichier. Dans notre CI : - le job GitHub Actions **rĂ©ussit** si la couverture est ≄ 80 % - il **Ă©choue** si la couverture descend sous 80 %. En local, tu peux Ă©galement mesurer la couverture ``php artisan test --coverage --min=80 --coverage-html ./report`` L'option ``--coverage-html ./report`` permet de gĂ©nĂ©rer un rapport, mesurant la couverture de notre application. ![couverture](./data/couverture.png){: .center} đŸ˜„ Ici le taux est de $62.45%$ donc trĂšs largement en dessous du seuil de `--coverage --min=80`. Donc si on laisse ce taux dans la CI, l'Ă©chec est assurĂ© ! !!! question "Taux de couverture" === "tests.yml" ▶ Modifier le fichier `tests.yml` pour vous assurer que le taux de couverture de 80% ne soit pas bloquant. === "Solution" ```bash run: php artisan test --coverage --min=80 || true ``` ??? warning "Fix problem Xdebug's coverage mode" symptĂŽmes : ```bash (base) PS C:\wamp64\www\todo2026> php artisan test --coverage --min=80 ERROR Code coverage driver not available. Did you set Xdebug's coverage mode? ``` Dans le "bon" `php.ini`, vĂ©rifier ```bash xdebug.mode=develop,coverage xdebug.start_with_request=no ``` Cela transforme la couverture en **garde-fou qualitĂ©** :
si un dĂ©veloppeur commente des tests ou ajoute beaucoup de code non testĂ©, la CI refusera la Pull Request ou signalera le problĂšme sur le push. ### 8.3. Lancer la CI aprĂšs les modifications ✅ Une fois le workflow en place, tu peux tester le pipeline complet :
vendor/bin/pint
php artisan test

git add .
git commit -m "Intégration de l'environnement de tests et de la couverture"
git push -u origin main
Sur **GitHub**, onglet Actions, tu dois voir le workflow tests s’exĂ©cuter automatiquement Ă  chaque push / pull request. ![tests](./data/tests.png){: .center} ??? warning "Fix probleme Vite" Verifier dans vos ``*.blade.php`` pour que le SASS soient utiliser (``app.blade.php``, ``guest.blade.php``) ```php @vite(['resources/sass/app.scss', 'resources/js/app.js']) ``` dans `vite.config.js` ```php