Chapitre 14.3 — ApiTestCase : tester l’API
Partie 14 : Tests & qualité — Chapitre 3/7 Version de référence : API Platform 4 / Symfony 7.4.
⏱️ TL;DR
- API Platform fournit
ApiTestCase: un client HTTP de test orienté API, avec des assertions JSON dédiées.- On teste chaque opération : 201 à la création, 200 en lecture, 204 à la suppression, 422 sur validation, 401/403 sur sécurité.
assertJsonContains([...])vérifie une partie du JSON ;assertMatchesResourceItemJsonSchema(...)valide la forme contre le schéma auto-généré.- Auth JWT : on obtient un token (login) et on l’envoie dans l’en-tête
Authorizationdes requêtes de test.- C’est le filet de sécurité du contrat d’API que consomme Next.js (Partie 11).
- Pour un dev Next.js : c’est tester votre API comme le ferait un client (statuts + JSON), garantissant que le contrat ne casse pas.
🎯 Objectifs
- Écrire des tests d’API (CRUD, statuts, JSON).
- Valider la forme des réponses contre le schéma.
- Tester l’authentification et la sécurité.
- Couvrir validation et erreurs.
1. Le client de test API
// tests/Api/BookingApiTest.php
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
final class BookingApiTest extends ApiTestCase
{
public function testGetCollectionRequiresAuth(): void
{
static::createClient()->request('GET', '/api/bookings');
self::assertResponseStatusCodeSame(401); // non authentifié → 401
}
}ApiTestCase étend KernelTestCase : on a accès au conteneur (repository, fixtures) et à un client HTTP orienté API.
2. S’authentifier avec un JWT
Pour tester les endpoints protégés, on obtient un JWT (via le login) et on l’attache :
private function authToken(string $email = 'admin@booklab.test', string $password = 'secret'): string
{
$response = static::createClient()->request('POST', '/api/login_check', [
'json' => ['email' => $email, 'password' => $password],
]);
return $response->toArray()['token'];
}
public function testCreateBooking(): void
{
$token = $this->authToken();
$response = static::createClient()->request('POST', '/api/bookings', [
'auth_bearer' => $token,
'json' => ['customerEmail' => 'client@example.com', 'start' => '2026-09-01T10:00:00Z'],
]);
self::assertResponseStatusCodeSame(201); // créé
self::assertJsonContains(['customerEmail' => 'client@example.com']); // le JSON contient ça
}3. Les assertions JSON
| Assertion | Vérifie |
|---|---|
assertResponseStatusCodeSame(201) | statut exact |
assertJsonContains([...]) | le JSON contient ces clés/valeurs (partiel) |
assertMatchesResourceItemJsonSchema(Booking::class) | la forme correspond au schéma d’un item |
assertMatchesResourceCollectionJsonSchema(Booking::class) | idem pour une collection |
L’assertion de JSON Schema est puissante : elle vérifie que la réponse respecte la structure générée par API Platform (types, champs requis). Si vous cassez un groupe de sérialisation (Partie 11.2), ce test échoue — protégeant le contrat consommé par Next.js.
4. Tester la validation et la sécurité
public function testCreateWithInvalidEmailReturns422(): void
{
$token = $this->authToken();
static::createClient()->request('POST', '/api/bookings', [
'auth_bearer' => $token,
'json' => ['customerEmail' => 'pas-un-email', 'start' => '2026-09-01T10:00:00Z'],
]);
self::assertResponseStatusCodeSame(422); // validation échoue
self::assertJsonContains(['violations' => [['propertyPath' => 'customerEmail']]]);
}
public function testUserCannotDeleteOthersBooking(): void
{
$token = $this->authToken('user@booklab.test', 'secret'); // simple utilisateur
static::createClient()->request('DELETE', '/api/bookings/1', ['auth_bearer' => $token]);
self::assertResponseStatusCodeSame(403); // le voter refuse
}On couvre ainsi les trois piliers : fonctionnel (CRUD marche), validation (422 sur données invalides), sécurité (401/403 selon l’auth et les voters). C’est exactement ce qui garantit que l’API reste fiable pour le front.
5. Un test complet du cycle CRUD
public function testFullCrudCycle(): void
{
$token = $this->authToken();
$client = static::createClient();
// CREATE
$created = $client->request('POST', '/api/bookings', [
'auth_bearer' => $token,
'json' => ['customerEmail' => 'c@e.com', 'start' => '2026-09-01T10:00:00Z'],
])->toArray();
self::assertResponseStatusCodeSame(201);
$iri = $created['@id'] ?? "/api/bookings/{$created['id']}";
// READ
$client->request('GET', $iri, ['auth_bearer' => $token]);
self::assertResponseIsSuccessful();
// DELETE
$client->request('DELETE', $iri, ['auth_bearer' => $this->authToken()]); // admin
self::assertResponseStatusCodeSame(204);
}⚠️ Piège isolation (rappel) : ces tests écrivent en base. Comme au chapitre 14.2, utilisez DAMADoctrineTestBundle (rollback par test) et des fixtures pour un état de départ connu. Un test d’API qui dépend de données créées par un autre test est fragile. Chaque test doit être autonome.
💡 Pour un dev Next.js : ces tests d’API sont votre contrat. Combinés à la génération de types depuis OpenAPI (Partie 11.8), ils forment une double garantie : le back respecte le schéma (ApiTestCase) et le front est typé dessus (openapi-typescript). Si vous changez l’API en cassant le contrat, soit le test échoue, soit
tscéchoue — vous êtes protégé des deux côtés.
✏️ Exercices
Exercice 1. Écrivez un test vérifiant qu’une création avec seats négatif renvoie 422.
✅ Solution
public function testNegativeSeatsIsRejected(): void
{
$token = $this->authToken();
static::createClient()->request('POST', '/api/bookings', [
'auth_bearer' => $token,
'json' => ['customerEmail' => 'c@e.com', 'start' => '2026-09-01T10:00:00Z', 'seats' => -1],
]);
self::assertResponseStatusCodeSame(422);
self::assertJsonContains(['violations' => [['propertyPath' => 'seats']]]);
}(Suppose une contrainte #[Assert\Positive] sur seats, Partie 9.3.)
Exercice 2. Que garantit assertMatchesResourceItemJsonSchema(Booking::class) et pourquoi est-ce précieux ?
✅ Solution
Il vérifie que la réponse respecte la structure (schéma JSON) générée par API Platform pour cette resource : bons champs, bons types, champs requis présents. C’est précieux car si vous cassez un groupe de sérialisation (retirez un champ exposé, changez un type), le test échoue — protégeant le contrat d’API que consomme le front Next.js. C’est un garde-fou contre les régressions de contrat.
Exercice 3. Pourquoi tester à la fois 422 (validation) et 403 (sécurité) sur les endpoints ?
✅ Solution
Parce que ce sont deux préoccupations distinctes et critiques : 422 vérifie que les données invalides sont rejetées proprement (le front s’appuie dessus pour afficher les erreurs) ; 403 vérifie que les voters/rôles empêchent réellement une action interdite (la sécurité serveur, non contournable). Tester les deux garantit que l’API est à la fois robuste (validation) et sûre (autorisation) — les deux piliers d’une API sérieuse.
🧠 Quiz de révision
1. Qu’apporte ApiTestCase ?
Un client HTTP de test orienté API avec des assertions JSON dédiées (assertJsonContains, assertMatchesResourceItemJsonSchema), en plus de l’accès au conteneur.
2. Comment teste-t-on un endpoint protégé par JWT ?
On obtient un token via POST /api/login_check, puis on l’envoie avec 'auth_bearer' => $token dans les requêtes de test.
3. Quel statut attend-on à la création, à la suppression, sur validation invalide ?
201 (créé), 204 (supprimé, sans corps), 422 (validation échoue).
4. Que protège l’assertion de JSON Schema ?
Le contrat de forme de l’API : si un groupe de sérialisation casse, le test échoue — protégeant le front qui en dépend.
5. Comment garder ces tests isolés ?
Base de test + DAMADoctrineTestBundle (rollback par test) + fixtures pour un état de départ connu ; chaque test autonome.
Prochain chapitre : 14.4 — Fixtures, mocks & data providers — préparer des données, isoler, et paramétrer les tests.