Tests

Tests

Niang\Core\Testing\TestCase simule des requêtes en appelant directement le Router, sans serveur HTTP réel — vérifié contre packages/foundation/src/Testing/*.php et un vrai test du framework (PasswordResetTest).

TestCase : simuler une requête

php

namespace Tests\Feature;

use Niang\Core\Testing\TestCase;

class HomeTest extends TestCase
{
    public function test_home_page_loads(): void
    {
        $this->get('/')->assertOk()->assertSee('NiangPro');
    }
}

get(), post($uri, $data), put(), patch(), delete() — et call($methode, $uri, $data, $headers) en dessous de tous. Chacun construit une Request et la fait passer par Application::handle(), exactement le chemin qu'emprunterait une vraie requête HTTP (middleware compris).

setUp() reconstruit une Application neuve à chaque test (routes rechargées, Service Providers ré-exécutés), réinitialise Event et Queue (sans quoi les listeners s'accumuleraient d'un test à l'autre), et joue les migrations en attente. APP_ENV=testing (positionné par phpunit.xml) charge .env.testing à la place de .env :

.env.testing
APP_ENV=testing
DB_CONNECTION=sqlite
DB_DATABASE=:memory:

Une base SQLite en mémoire, jamais storage/database.sqlite : la suite de tests ne touche jamais aux données de développement local.

Assertions sur la réponse

get()/post()/... retournent un TestResponse chaînable :

MéthodeVérifie
assertStatus($code)Code HTTP exact (affiche le corps de la réponse si l'assertion échoue).
assertOk()Raccourci pour assertStatus(200).
assertRedirect($to = null)301/302, et optionnellement la valeur de l'en-tête Location.
assertSee($texte) / assertDontSee($texte)Présence/absence d'une sous-chaîne dans le corps.
assertJson($subset)Le JSON décodé contient au moins ces clés/valeurs ; les objets imbriqués sont comparés de la même façon (['data' => ['email' => ...]] ignore les autres champs de data), une liste exactement.
json() / content() / header($clé) / status()Accès brut, pour des assertions PHPUnit directes.

RefreshDatabase

La base :memory: survit pour tout le run PHPUnit (contrairement à un vrai process web) : un test qui écrit en base doit annuler ses propres écritures pour ne pas polluer les suivants.

php
use Niang\Core\Testing\RefreshDatabase;
use Niang\Core\Testing\TestCase;

class PasswordResetTest extends TestCase
{
    use RefreshDatabase;

    public function test_a_user_can_reset_their_password_end_to_end(): void
    {
        User::create(['name' => 'Awa', 'email' => 'awa@example.test', 'password' => Hash::make('ancien-mdp-1234')]);
        // ... écrit en base sans crainte : tout est annulé après ce test.
    }
}

Le trait enrobe chaque test dans une transaction (DB::beginTransaction() en setUp(), DB::rollBack() en tearDown()) — à ajouter (use RefreshDatabase;) dans tout test qui écrit en base, jamais activé par défaut.

Mail::fake()

Bascule le driver mail sur 'array' pour la durée du test, quel que soit MAIL_MAILER dans .env.testing — voir Mail :

php
protected function tearDown(): void
{
    Mail::reset(); // sans quoi le driver 'array' resterait actif pour le test suivant
    parent::tearDown();
}

public function test_a_reset_link_is_emailed(): void
{
    Mail::fake();

    $this->post('/forgot-password', ['_token' => Csrf::token(), 'email' => 'awa@example.test'])
        ->assertRedirect('/forgot-password');

    $sent = Mail::sent();
    $this->assertCount(1, $sent);
    $this->assertSame('awa@example.test', $sent[0]['to']);
}

Mail::reset() en tearDown() est à la charge du test (comme Event::reset()/Queue::reset() sont, eux, automatiques dans TestCase::setUp()) — sans lui, Mail::fake() resterait actif pour le test suivant du même process.

Structure Unit / Feature / Database

phpunit.xml déclare trois suites, chacune sur son dossier :

SuiteContenu type
tests/Unit/Classes isolées (Container, Validator, Router...) — souvent sans étendre Niang\Core\Testing\TestCase, juste PHPUnit\Framework\TestCase.
tests/Feature/Requêtes complètes via Testing\TestCase — routes, middleware, contrôleurs, réponses.
tests/Database/Migrations, Query Builder, eager loading exécutés contre une vraie base (:memory:).
bash
./bin/niang make:test PostsPage   # génère tests/Unit/PostsPageTest.php
composer test                     # vendor/bin/phpunit

make:test génère toujours dans tests/Unit/, avec un stub qui étend PHPUnit\Framework\TestCase directement (pas Niang\Core\Testing\TestCase) : à changer manuellement l'use et l'emplacement du fichier pour un test Feature ou Database qui a besoin de l'application complète.

Suite de tests de sécurité

bash
vendor/bin/phpunit --testsuite=Security

tests/Security/ regroupe une attaque par test : injection SQL, XSS, CSRF, redirection ouverte, en-tête Host, fixation de session, cookies, traversée de chemin, fichiers déguisés, affectation de masse, IDOR, limitation de débit, en-têtes de sécurité, debug en production. Toute faille corrigée y ajoute son test.

Couverture de code

Pour le framework lui-même, la CI mesure la couverture (extension pcov ; en local, pcov ou xdebug) et impose un seuil global (78 %) et un seuil par composant critique : Router, Container, Database, Auth (double authentification et OAuth compris), Validation, HTTP, sécurité. Les seuils sont dans tools/coverage-check.php.

bash
vendor/bin/phpunit --coverage-clover build/clover.xml
php tools/coverage-check.php build/clover.xml

Stabilité de l'API et dépréciations

docs/API_STABILITY.md classe chaque classe publique : stable (aucune rupture avant la prochaine version majeure), expérimentale (@experimental : OpenAPI, OAuth, permissions, SSE, disque S3...) ou interne (@internal). Une API supprimée est d'abord dépréciée pendant au moins une version : @deprecated et trigger_deprecation(), consignée dans les logs en production. Le cœur passe PHPStan au niveau 7 et déclare strict_types partout.

Contribuer au framework

Le guide CONTRIBUTING.md détaille l'installation, les vérifications à lancer (composer test, lint, analyse) et ce qu'on attend d'une pull request. Une faille de sécurité se signale en privé, jamais dans une issue : voir SECURITY.md.

⏱ 7.95 ms 🗄 0 requête(s) SQL 🧠 4.00 MB ↩ 200