Testing Utilities

API Platform provides a set of useful utilities dedicated to API testing. For an overview of how to test an API Platform app, be sure to read the testing cookbook first.

Test and Assertions screencast
Watch the API Tests & Assertions screencast

The Test HttpClient

API Platform provides its own implementation of the Symfony HttpClient’s interfaces, tailored to be used directly in PHPUnit test classes.

While all the convenient features of Symfony HttpClient are available and usable directly, under the hood the API Platform implementation manipulates the Symfony HttpKernel directly to simulate HTTP requests and responses. This approach results in a huge performance boost compared to triggering real network requests. It also allows access to the Symfony HttpKernel and to all your services via the Dependency Injection Container. Reuse them to run, for instance, SQL queries or requests to external APIs directly from your tests.

Install the symfony/http-client and symfony/browser-kit packages to enabled the API Platform test client:

docker compose exec php \
    composer require symfony/browser-kit symfony/http-client

To use the testing client, your test class must extend the ApiTestCase class:

<?php
// api/tests/BooksTest.php

namespace App\Tests;

use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;

class BooksTest extends ApiTestCase
{
    public function testGetCollection(): void
    {
        $response = static::createClient()->request('GET', '/books');
        // your assertions here...
    }
}

Refer to the Symfony HttpClient documentation to discover all the features of the client (custom headers, JSON encoding and decoding, HTTP Basic and Bearer authentication and cookies support, among other things).

Note that you can create your own test case class extending the ApiTestCase. For example to set up a Json Web Token authentication:

<?php
// api/tests/AbstractTest.php
namespace App\Tests;

use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use ApiPlatform\Symfony\Bundle\Test\Client;
use Hautelook\AliceBundle\PhpUnit\RefreshDatabaseTrait;

abstract class AbstractTest extends ApiTestCase
{
    private ?string $token = null;

    use RefreshDatabaseTrait;

    public function setUp(): void
    {
        self::bootKernel();
    }

    protected function createClientWithCredentials($token = null): Client
    {
        $token = $token ?: $this->getToken();

        return static::createClient([], ['headers' => ['authorization' => 'Bearer '.$token]]);
    }

    /**
     * Use other credentials if needed.
     */
    protected function getToken($body = []): string
    {
        if ($this->token) {
            return $this->token;
        }

        $response = static::createClient()->request('POST', '/login', ['json' => $body ?: [
            'username' => '[email protected]',
            'password' => '$3cr3t',
        ]]);

        $this->assertResponseIsSuccessful();
        $data = $response->toArray();
        $this->token = $data['token'];

        return $data['token'];
    }
}

Use it by extending the AbstractTest class. For example this class tests the /users resource accessibility where only the admin can retrieve the collection:

<?php
namespace App\Tests;

final class UsersTest extends AbstractTest
{
    public function testAdminResource()
    {
        $response = $this->createClientWithCredentials()->request('GET', '/users');
        $this->assertResponseIsSuccessful();
    }

    public function testLoginAsUser()
    {
        $token = $this->getToken([
            'username' => '[email protected]',
            'password' => '$3cr3t',
        ]);

        $response = $this->createClientWithCredentials($token)->request('GET', '/users');
        $this->assertJsonContains(['hydra:description' => 'Access Denied.']);
        $this->assertResponseStatusCodeSame(403);
    }
}

API Test Assertions

In addition to the built-in ones, API Platform provides convenient PHPUnit assertions dedicated to API testing:

<?php
// api/tests/MyTest.php

namespace App\Tests;

use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;

class MyTest extends ApiTestCase
{
    public function testSomething(): void
    {
        // static::createClient()->request(...);

        // Asserts that the returned JSON is equal to the passed one
        $this->assertJsonEquals(/* a JSON document as an array or as a string */);

        // Asserts that the returned JSON is a superset of the passed one
        $this->assertJsonContains(/* a JSON document as an array or as a string */);

        // justinrainbow/json-schema must be installed to use the following assertions

        // Asserts that the returned JSON matches the passed JSON Schema
        $this->assertMatchesJsonSchema(/* a JSON Schema as an array or as a string */);

        // Asserts that the returned JSON is validated by the JSON Schema generated for this resource by API Platform

        // For collections
        $this->assertMatchesResourceCollectionJsonSchema(YourApiResource::class);
        // And for items
        $this->assertMatchesResourceItemJsonSchema(YourApiResource::class);
    }
}

There is also a method to find the IRI matching a given resource and some criteria:

<?php
// api/tests/BooksTest.php

namespace App\Tests;

use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;

class BooksTest extends ApiTestCase
{
    public function testFindBook(): void
    {
        // Asserts that the returned JSON is equal to the passed one
        $iri = $this->findIriBy(Book::class, ['isbn' => '9780451524935']);
        static::createClient()->request('GET', $iri);
        $this->assertResponseIsSuccessful();
    }
}

HTTP Test Assertions

All test assertions provided by Symfony (assertions for status codes, headers, cookies, XML documents…) can be used out of the box with the API Platform test client:

<?php
// api/tests/BooksTest.php

namespace App\Tests;

use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;

class BooksTest extends ApiTestCase
{
    public function testGetCollection(): void
    {
        static::createClient()->request('GET', '/books');

        $this->assertResponseIsSuccessful();
        $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
    }
}

Check out the dedicated Symfony documentation entry.

You can also help us improve the documentation of this page.

Made with love by

Les-Tilleuls.coop can help you design and develop your APIs and web projects, and train your teams in API Platform, Symfony, Next.js, Kubernetes and a wide range of other technologies.

Learn more

Copyright © 2023 Kévin Dunglas

Sponsored by Les-Tilleuls.coop