Dans le [Module 6], nous avons connecté des indicateurs financiers temps réel, des graphiques dynamiques et des mécanismes d’export asynchrones.
À ce stade du projet, modifier un composant ou mettre à jour une dépendance sans filet de sécurité devient risqué. Tester manuellement chaque formulaire, chaque clic de bouton ou chaque règle de gestion à la main prend un temps considérable et laisse passer des régressions.
Pour garantir la fiabilité d’EquipFlow, nous mettons en place une suite de tests automatisés avec Pest PHP. Grâce aux utilitaires de test natifs de Filament, nous allons vérifier la logique métier, la validation des formulaires et le respect des permissions d’accès sans avoir besoin de lancer un navigateur lourd (comme Playwright ou Selenium).
Mise en place de Pest PHP
Installez Pest et son plugin dédié à Laravel :
composer require pestphp/pest:^3.0 pestphp/pest-plugin-laravel:^3.0 --dev
php artisan pest:install
Configurez l’environnement de test dans le fichier phpunit.xml pour exécuter les tests sur une base de données SQLite en mémoire :
<php>
<env name="APP_ENV" value="testing"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_STORE" value="array"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="MAIL_MAILER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
</php>
Vérifiez le fonctionnement immédiat :
./vendor/bin/pest
Tester les règles métier et les Enums en isolation
Commençons par tester le scope scopeAvailable défini au Module 4 sur le modèle Equipment. Ce scope garantit qu’un matériel actuellement loué ne peut pas être proposé pour une autre assignation.
Créez le fichier de test unitaire :
php artisan make:test Models/EquipmentTest --pest --unit
Dans tests/Unit/Models/EquipmentTest.php :
declare(strict_types=1);
use App\Enums\AssignmentStatus;
use App\Models\Assignment;
use App\Models\Client;
use App\Models\Equipment;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(Tests\TestCase::class, RefreshDatabase::class);
it('ne liste que les équipements réellement disponibles', function () {
// 1. Création d'un équipement libre
$availableEquipment = Equipment::factory()->create();
// 2. Création d'un équipement en cours de location active
$busyEquipment = Equipment::factory()->create();
$client = Client::factory()->create();
Assignment::factory()->create([
'client_id' => $client->id,
'equipment_id' => $busyEquipment->id,
'status' => AssignmentStatus::Active,
]);
// 3. Exécution du scope
$availableList = Equipment::available()->pluck('id');
// 4. Assertions
expect($availableList)
->toContain($availableEquipment->id)
->not->toContain($busyEquipment->id);
});
Tester les interfaces Filament sans navigateur
Filament fournit des helpers d’assertion basés sur Livewire. Vous pouvez tester le rendu des composants, la saisie des champs et le déclenchement des actions en quelques millisecondes.
Créez le test de fonctionnalité :
php artisan make:test Filament/AssignmentResourceTest --pest
Dans tests/Feature/Filament/AssignmentResourceTest.php :
Valider le rendu de la table et des colonnes
declare(strict_types=1);
namespace Tests\Feature\Filament;
use App\Enums\UserRole;
use App\Filament\Resources\AssignmentResource;
use App\Models\Assignment;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Spatie\Permission\Models\Role;
uses(RefreshDatabase::class);
beforeEach(function () {
// Création du rôle et de l'utilisateur de test
Role::firstOrCreate(['name' => UserRole::Admin->value]);
$this->admin = User::factory()->create();
$this->admin->assignRole(UserRole::Admin->value);
});
it('affiche la liste des affectations correctement', function () {
$assignment = Assignment::factory()->create();
$this->actingAs($this->admin);
Livewire::test(AssignmentResource\Pages\ListAssignments::class)
->assertSuccessful()
->assertCanSeeTableRecords([$assignment]);
});
Valider la création d’une affectation et ses règles de validation
Testons que le formulaire refuse la création si un champ obligatoire est manquant, et valide l’enregistrement lorsque les données sont conformes :
use App\Enums\AssignmentStatus;
use App\Models\Client;
use App\Models\Equipment;
it('peut créer une nouvelle affectation via le formulaire Filament', function () {
$client = Client::factory()->create();
$equipment = Equipment::factory()->create();
$this->actingAs($this->admin);
Livewire::test(AssignmentResource\Pages\CreateAssignment::class)
->fillForm([
'client_id' => $client->id,
'equipment_id' => $equipment->id,
'monthly_cost' => '120.00',
'status' => AssignmentStatus::Active->value,
'assigned_at' => now()->format('Y-m-d'),
])
->call('create')
->assertHasNoFormErrors();
$this->assertDatabaseHas('assignments', [
'client_id' => $client->id,
'equipment_id' => $equipment->id,
'monthly_cost' => 120.00,
'status' => AssignmentStatus::Active->value,
]);
});
it('échoue si le loyer mensuel est absent', function () {
$this->actingAs($this->admin);
Livewire::test(AssignmentResource\Pages\CreateAssignment::class)
->fillForm([
'monthly_cost' => null,
])
->call('create')
->assertHasFormErrors(['monthly_cost' => 'required']);
});
Tester le déclenchement d’une Action personnalisée de Table
Dans le Module 4, nous avons implémenté l’action complete_assignment qui ouvre une modale pour restituer le matériel. Vérifions que cette action met bien à jour le statut en base :
it('clôture une affectation via l’action de table dédiée', function () {
$assignment = Assignment::factory()->create([
'status' => AssignmentStatus::Active,
'returned_at' => null,
]);
$this->actingAs($this->admin);
Livewire::test(AssignmentResource\Pages\ListAssignments::class)
->callTableAction('complete_assignment', $assignment, data: [
'returned_at' => now()->format('Y-m-d'),
'return_notes' => 'Matériel inspecté : aucun dommage.',
])
->assertHasNoTableActionErrors();
expect($assignment->fresh())
->status->toBe(AssignmentStatus::Completed)
->returned_at->not->toBeNull()
->notes->toContain('aucun dommage');
});
Tester les permissions et les Policies
Vérifions que notre ClientPolicy (mise en place au Module 5) interdit bien la suppression aux utilisateurs qui n’ont pas le rôle admin :
php artisan make:test Policies/ClientPolicyTest --pest
Dans tests/Feature/Policies/ClientPolicyTest.php :
declare(strict_types=1);
namespace Tests\Feature\Policies;
use App\Enums\UserRole;
use App\Filament\Resources\ClientResource;
use App\Models\Client;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Spatie\Permission\Models\Role;
uses(RefreshDatabase::class);
it('interdit la suppression d’un client à un profil technicien', function () {
Role::firstOrCreate(['name' => UserRole::Technician->value]);
$technician = User::factory()->create();
$technician->assignRole(UserRole::Technician->value);
$client = Client::factory()->create();
$this->actingAs($technician);
// Vérifie que l'action de suppression n'existe pas pour ce profil
Livewire::test(ClientResource\Pages\ListClients::class)
->assertTableActionHidden('delete', $client);
});
Automatisation dans la chaîne de qualité
Complétez le script analyse dans votre fichier composer.json pour exécuter la suite de tests en même temps que Pint et Larastan :
"scripts": {
"test": "./vendor/bin/pest",
"analyse": [
"./vendor/bin/pint --test",
"./vendor/bin/phpstan analyse",
"./vendor/bin/pest"
]
}
Lancez la commande complète :
composer run analyse
En une poignée de secondes, l’ensemble de votre base de code est formaté, inspecté au niveau des types stricts et validé fonctionnellement par vos tests automatisés.
Résumé du Module 7
Grâce à cette stratégie de test ciblée :
- Pest PHP valide l’intégrité de vos modèles et scopes sans overhead.
- Les assertions Livewire/Filament testent vos formulaires, validations et actions modales sans la lenteur d’un navigateur headless.
- Les permissions RBAC sont verrouillées par des tests automatisés, éliminant le risque de failles d’autorisation.
Dans le Module 8 (l’ultime volet de la série), nous aborderons la dernière ligne droite : Déploiement, Optimisation et Production (caches Laravel, tuning d’OPcache sous PHP 8.6, gestion des Workers de Queue et monitoring des erreurs).



