Testing the API

Now that you have a functional API, you should write tests to ensure it has no bugs, and to prevent future regressions. Some would argue that it's even better to write tests first.

機能する API ができたので、バグがないことを確認し、将来のリグレッションを防ぐためにテストを作成する必要があります。最初にテストを作成する方がよいと主張する人もいます。

API Platform provides a set of helpful testing utilities to write unit tests, functional tests, and to create test fixtures.

API プラットフォームには、単体テスト、機能テスト、およびテスト フィクスチャの作成に役立つ一連のテスト ユーティリティが用意されています。

Let's learn how to use them!

それらの使い方を学びましょう!

Tests and Assertions screencast
Watch the Tests & Assertions screencast

テストとアサーションのスクリーンキャストを見る

In this article you'll learn how to use:

この記事では、次の使用方法を学習します。

  • PHPUnit, a testing framework to cover your classes with unit tests and to write API-oriented functional tests thanks to its API Platform and Symfony integrations.
    PHPUnit は、API プラットフォームと Symfony の統合により、クラスを単体テストでカバーし、API 指向の機能テストを作成するためのテスト フレームワークです。
  • Alice and its Symfony integration, an expressive fixtures generator to write data fixtures.
    Alice とその Symfonyintegration は、データ フィクスチャを記述するための表現力豊かなフィクスチャ ジェネレータです。

Official Symfony recipes are provided for both tools.

公式の Symfony レシピは両方のツールに提供されています。

Creating Data Fixtures

Before creating your functional tests, you will need a dataset to pre-populate your API and be able to test it.

機能テストを作成する前に、API を事前設定してテストできるようにするためのデータセットが必要です。

First, install Alice:

まず、アリスをインストールします。

docker compose exec php \
    composer require --dev alice

Thanks to Symfony Flex, Alice (and AliceBundle) are ready to use! Place your data fixtures files in a directory named fixtures/.

Symfony Flex のおかげで、Alice (および AliceBundle) をすぐに使用できます。データ フィクスチャ ファイルは、fixtures/ という名前のディレクトリに配置します。

Then, create some fixtures for the bookstore API you created in the tutorial:

次に、チュートリアルで作成した書店 API 用のいくつかのフィクスチャを作成します。

# api/fixtures/books.yaml
App\Entity\Book:
    book_{1..100}:
        isbn: <isbn13()>
        title: <sentence(4)>
        description: <text()>
        author: <name()>
        publicationDate: <dateTimeImmutable()>
# api/fixtures/reviews.yaml
App\Entity\Review:
    review_{1..200}:
        rating: <numberBetween(0, 5)>
        body: <text()>
        author: <name()>
        publicationDate: <dateTimeImmutable()>
        book: '@book_*'

You can now load your fixtures in the database with the following command:

次のコマンドを使用して、フィクスチャをデータベースにロードできるようになりました。

docker compose exec php \
    bin/console hautelook:fixtures:load

To learn more about fixtures, take a look at the documentation of Alice and AliceBundle. The list of available generators as well as a cookbook explaining how to create custom generators can be found in the documentation of Faker, the library used by Alice under the hood.

フィクスチャの詳細については、Alice と AliceBundle のドキュメントを参照してください。使用可能なジェネレータのリストと、カスタム ジェネレータの作成方法を説明するクックブックは、Alice が内部で使用するライブラリである Faker のドキュメントにあります。

Writing Functional Tests

Now that you have some data fixtures for your API, you are ready to write functional tests with PHPUnit.

API 用のデータ フィクスチャがいくつかあるので、PHPUnit を使用して機能テストを作成する準備が整いました。

Install the Symfony test pack (which includes PHPUnit and PHPUnit Bridge), Symfony HttpClient (the API Platform test client is built on top of Symfony HttpClient, and allows to leverage all its features) and JSON Schema for PHP (used by API Platform to provide JSON Schema test assertions):

Symfony テスト パック (PHPUnit と PHPUnit Bridge を含む)、Symfony HttpClient (API プラットフォーム テスト クライアントは Symfony HttpClient の上に構築され、そのすべての機能を活用できます)、および PHP 用の JSON スキーマ (API プラットフォームが提供するために使用) をインストールします。 JSON スキーマ テスト アサーション):

docker compose exec php \
    composer require --dev symfony/test-pack symfony/http-client justinrainbow/json-schema

Your API is ready to be functionally tested. Create your test classes under the tests/ directory.

API の機能をテストする準備ができました。 tests/ ディレクトリの下にテスト クラスを作成します。

Here is an example of functional tests specifying the behavior of the bookstore API you created in the tutorial:

チュートリアルで作成した書店 API の動作を指定する機能テストの例を次に示します。

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

namespace App\Tests;

use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use App\Entity\Book;
use Hautelook\AliceBundle\PhpUnit\RefreshDatabaseTrait;

class BooksTest extends ApiTestCase
{
    // This trait provided by AliceBundle will take care of refreshing the database content to a known state before each test
    use RefreshDatabaseTrait;

    public function testGetCollection(): void
    {
        // The client implements Symfony HttpClient's `HttpClientInterface`, and the response `ResponseInterface`
        $response = static::createClient()->request('GET', '/books');

        $this->assertResponseIsSuccessful();
        // Asserts that the returned content type is JSON-LD (the default)
        $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');

        // Asserts that the returned JSON is a superset of this one
        $this->assertJsonContains([
            '@context' => '/contexts/Book',
            '@id' => '/books',
            '@type' => 'hydra:Collection',
            'hydra:totalItems' => 100,
            'hydra:view' => [
                '@id' => '/books?page=1',
                '@type' => 'hydra:PartialCollectionView',
                'hydra:first' => '/books?page=1',
                'hydra:last' => '/books?page=4',
                'hydra:next' => '/books?page=2',
            ],
        ]);

        // Because test fixtures are automatically loaded between each test, you can assert on them
        $this->assertCount(30, $response->toArray()['hydra:member']);

        // Asserts that the returned JSON is validated by the JSON Schema generated for this resource by API Platform
        // This generated JSON Schema is also used in the OpenAPI spec!
        $this->assertMatchesResourceCollectionJsonSchema(Book::class);
    }

    public function testCreateBook(): void
    {
        $response = static::createClient()->request('POST', '/books', ['json' => [
            'isbn' => '0099740915',
            'title' => 'The Handmaid\'s Tale',
            'description' => 'Brilliantly conceived and executed, this powerful evocation of twenty-first century America gives full rein to Margaret Atwood\'s devastating irony, wit and astute perception.',
            'author' => 'Margaret Atwood',
            'publicationDate' => '1985-07-31T00:00:00+00:00',
        ]]);

        $this->assertResponseStatusCodeSame(201);
        $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
        $this->assertJsonContains([
            '@context' => '/contexts/Book',
            '@type' => 'Book',
            'isbn' => '0099740915',
            'title' => 'The Handmaid\'s Tale',
            'description' => 'Brilliantly conceived and executed, this powerful evocation of twenty-first century America gives full rein to Margaret Atwood\'s devastating irony, wit and astute perception.',
            'author' => 'Margaret Atwood',
            'publicationDate' => '1985-07-31T00:00:00+00:00',
            'reviews' => [],
        ]);
        $this->assertMatchesRegularExpression('~^/books/\d+$~', $response->toArray()['@id']);
        $this->assertMatchesResourceItemJsonSchema(Book::class);
    }

    public function testCreateInvalidBook(): void
    {
        static::createClient()->request('POST', '/books', ['json' => [
            'isbn' => 'invalid',
        ]]);

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

        $this->assertJsonContains([
            '@context' => '/contexts/ConstraintViolationList',
            '@type' => 'ConstraintViolationList',
            'hydra:title' => 'An error occurred',
            'hydra:description' => 'isbn: This value is neither a valid ISBN-10 nor a valid ISBN-13.
title: This value should not be blank.
description: This value should not be blank.
author: This value should not be blank.
publicationDate: This value should not be null.',
        ]);
    }

    public function testUpdateBook(): void
    {
        $client = static::createClient();
        // findIriBy allows to retrieve the IRI of an item by searching for some of its properties.
        // ISBN 9786644879585 has been generated by Alice when loading test fixtures.
        // Because Alice use a seeded pseudo-random number generator, we're sure that this ISBN will always be generated.
        $iri = $this->findIriBy(Book::class, ['isbn' => '9781344037075']);

        $client->request('PUT', $iri, ['json' => [
            'title' => 'updated title',
        ]]);

        $this->assertResponseIsSuccessful();
        $this->assertJsonContains([
            '@id' => $iri,
            'isbn' => '9781344037075',
            'title' => 'updated title',
        ]);
    }

    public function testDeleteBook(): void
    {
        $client = static::createClient();
        $iri = $this->findIriBy(Book::class, ['isbn' => '9781344037075']);

        $client->request('DELETE', $iri);

        $this->assertResponseStatusCodeSame(204);
        $this->assertNull(
            // Through the container, you can access all your services from the tests, including the ORM, the mailer, remote API clients...
            static::getContainer()->get('doctrine')->getRepository(Book::class)->findOneBy(['isbn' => '9781344037075'])
        );
    }

    public function testLogin(): void
    {
        $response = static::createClient()->request('POST', '/login', ['json' => [
            'email' => 'admin@example.com',
            'password' => 'admin',
        ]]);

        $this->assertResponseIsSuccessful();
    }
}

As you can see, the example uses the trait RefreshDatabaseTrait from AliceBundle which will, at the beginning of each test, purge the database, load fixtures, begin a transaction, and, at the end of each test, roll back the transaction previously begun. Because of this, you can run your tests without worrying about fixtures.

ご覧のとおり、この例では、AliceBundle の特性 RefreshDatabaseTrait を使用しています。これは、各テストの開始時にデータベースをパージし、フィクスチャをロードし、トランザクションを開始し、各テストの終了時に、以前に開始されたトランザクションをロールバックします。このため、フィクスチャを気にせずにテストを実行できます。

There is one caveat though: in some tests, it is necessary to perform multiple requests in one test, for example when creating a user via the API and checking that a subsequent login using the same password works. However, the client will by default reboot the kernel, which will reset the database. You can prevent this by adding $client->disableReboot(); to such tests.

ただし、注意点が 1 つあります。一部のテストでは、1 つのテストで複数のリクエストを実行する必要があります。たとえば、API を介してユーザーを作成し、同じパスワードを使用した後続のログインが機能することを確認する場合です。ただし、クライアントはデフォルトでカーネルを再起動し、データベースをリセットします。 $client->disableReboot(); を追加することでこれを防ぐことができます。そのようなテストに。

All you have to do now is to run your tests:

あとは、テストを実行するだけです。

docker compose exec php \
    bin/phpunit

If everything is working properly, you should see OK (5 tests, 17 assertions). Your REST API is now properly tested!

すべてが正常に機能している場合は、OK (5 つのテスト、17 のアサーション) が表示されます。これで、REST API が適切にテストされました!

Check out the testing documentation to discover the full range of assertions and other features provided by API Platform's test utilities.

テスト ドキュメントをチェックして、API プラットフォームのテスト ユーティリティが提供するアサーションやその他の機能の全範囲を確認してください。

Writing Unit Tests

In addition to integration tests written using the helpers provided by ApiTestCase, all the classes of your project should be covered by unit tests. To do so, learn how to write unit tests with PHPUnit and its Symfony/API Platform integration.

ApiTestCase が提供するヘルパーを使用して作成された統合テストに加えて、プロジェクトのすべてのクラスを単体テストでカバーする必要があります。そのためには、PHPUnit とその Symfony/API プラットフォームの統合を使用して単体テストを作成する方法を学びます。

Additional and Alternative Testing Tools

You may also be interested in these alternative testing tools (not included in the API Platform distribution):

次の代替テスト ツール (API プラットフォーム ディストリビューションには含まれていません) にも興味があるかもしれません。

  • Behat and its Behatch extension, a behavior-driven development (BDD) framework to write the API specification as user stories and in natural language then execute these scenarios against the application to validate its behavior;
    Behat とその Behatch 拡張機能である動作駆動型開発 (BDD) フレームワーク。API 仕様をユーザー ストーリーとして自然言語で記述し、アプリケーションに対してこれらのシナリオを実行して動作を検証します。
  • Blackfire Player, a nice DSL to crawl HTTP services, assert responses, and extract data from HTML/XML/JSON responses (see example in API Platform Demo);
    HTTP サービスをクロールし、応答をアサートし、HTML/XML/JSON 応答からデータを抽出するための優れた DSL である Blackfire Player (API プラットフォームのデモの例を参照)。
  • Postman tests (proprietary), create functional test for your API Platform project using a nice UI, benefit from the Swagger integration and run tests in the CI using newman;
    Postman テスト (独自)、優れた UI を使用した APIPlatform プロジェクトの機能テストの作成、Swagger 統合の恩恵、newman を使用した CI でのテストの実行。
  • PHP Matcher, the Swiss Army knife of JSON document testing.
    PHP Matcher、JSON ドキュメント テストのスイス アーミー ナイフ。

Using the API Platform Distribution for End-to-end Testing

If you would like to verify that your stack (including services such as the DBMS, web server, Varnish) works, you need end-to-end (E2E) testing.

スタック (DBMS、Web サーバー、Varnish などのサービスを含む) が機能することを確認したい場合は、エンド ツー エンド (E2E) テストが必要です。

It is also useful to do a smoke test to check that your application is working; for example, that the application's entrypoint is accessible. This could be used as a quick test after each Docker build to ensure that the Docker images are not broken.

スモーク テストを実行して、アプリケーションが動作していることを確認することも役立ちます。たとえば、アプリケーションのエントリポイントがアクセス可能であること。これは、Docker イメージが破損していないことを確認するための各 Docker ビルド後の簡単なテストとして使用できます。

Usually, this should be done with a production-like setup. For your convenience, you may run our Docker Compose setup for production locally.

通常、これはプロダクションのような設定で行う必要があります。便宜上、本番用の Docker Compose セットアップをローカルで実行できます。