# 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](https://martinfowler.com/bliki/TestDrivenDevelopment.html). API Platform provides a set of helpful testing utilities to write unit tests, functional tests, and to create [test fixtures](https://en.wikipedia.org/wiki/Test_fixture#Software). 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](https://phpunit.de/index.html), a testing framework to cover your classes with unit tests and to write API-oriented functional tests thanks to its API Platform and [Symfony](https://symfony.com/doc/current/testing.html) integrations. * [Alice](https://github.com/nelmio/alice) and [its Symfony integration](https://github.com/hautelook/AliceBundle#database-testing), an expressive fixtures generator to write data fixtures. Official [Symfony recipes](https://flex.symfony.com/) are provided for both tools. ## Creating Data Fixtures Before creating your functional tests, you will need a dataset to pre-populate your API and be able to test it. First, install [Alice](https://github.com/nelmio/alice): $ docker-compose exec php composer require --dev alice Thanks to Symfony Flex, Alice (and [AliceBundle](https://github.com/hautelook/AliceBundle)) are ready to use! Place your data fixtures files in a directory named `fixtures/`. Then, create some fixtures for [the bookstore API you created in the tutorial](index.md): ```yaml # api/fixtures/books.yaml App\Entity\Book: book_{1..100}: isbn: title: description: author: publicationDate: ``` ```yaml # api/fixtures/reviews.yaml App\Entity\Review: review_{1..200}: rating: body: author: publicationDate: 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](https://github.com/nelmio/alice) and [AliceBundle](https://github.com/hautelook/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](https://github.com/fzaninotto/Faker), the library used by Alice under the hood. ## Writing Functional Tests Now that you have some data fixtures for your API, you are ready to write functional tests with [PHPUnit](https://phpunit.de/index.html). Install the Symfony test pack (which includes PHPUnit and [PHPUnit Bridge](https://symfony.com/doc/current/components/phpunit_bridge.html)), [Symfony HttpClient](https://symfony.com/doc/current/components/http_client.html) (the API Platform test client is built on top of Symfony HttpClient, and allows to leverage all its features) and [JSON Schema for PHP](https://github.com/justinrainbow/json-schema) (used by API Platform to provide [JSON Schema](https://json-schema.org/) test assertions): $ docker-compose exec php composer require --dev test-pack http-client justinrainbow/json-schema Your API is ready to be functionally tested. Create your test classes under the `tests/` directory. Here is an example of functional tests specifying the behavior of [the bookstore API you created in the tutorial](index.md): ```php 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->assertRegExp('~^/books/\d+$~', $response->toArray()['@id']); $this->assertMatchesResourceItemJsonSchema(Book::class); } public function testCreateInvalidBook(): void { static::createClient()->request('POST', '/books', ['json' => [ 'isbn' => 'invalid', ]]); $this->assertResponseStatusCodeSame(400); $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::$container->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`](https://github.com/hautelook/AliceBundle#database-testing) from [AliceBundle](https://github.com/hautelook/AliceBundle/blob/master/README.md) 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. 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! Check out the [testing documentation](../core/testing.md) to discover the full range of assertions and other features provided by API Platform's test utilities. ## 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](https://en.wikipedia.org/wiki/Unit_testing). To do so, learn how to write unit tests with [PHPUnit](https://phpunit.de/index.html) and [its Symfony/API Platform integration](https://symfony.com/doc/current/testing.html). ## Additional and Alternative Testing Tools You may also be interested in these alternative testing tools (not included in the API Platform distribution): * [Behat](http://behat.org/en/latest/) and its [Behatch extension](https://github.com/Behatch/contexts), a [behavior-driven development (BDD)](https://en.wikipedia.org/wiki/Behavior-driven_development) framework to write the API specification as user stories and in natural language then execute these scenarios against the application to validate its behavior; * [Blackfire Player](https://blackfire.io/player), a nice DSL to crawl HTTP services, assert responses, and extract data from HTML/XML/JSON responses ([see example in API Platform Demo](https://github.com/api-platform/demo/blob/master/test-api.bkf)); * [Postman tests](https://www.getpostman.com/docs/writing_tests) (proprietary), create functional test for your API Platform project using a nice UI, benefit from [the Swagger integration](https://www.getpostman.com/docs/importing_swagger) and run tests in the CI using [newman](https://github.com/postmanlabs/newman); * [PHP Matcher](https://github.com/coduo/php-matcher), the Swiss Army knife of JSON document testing. ## 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](https://varnish-cache.org/)) works, you need [end-to-end (E2E) testing](https://wiki.c2.com/?EndToEndPrinciple). It is also useful to do a [smoke test](https://en.wikipedia.org/wiki/Smoke_testing_(software)) 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. Usually, this should be done with a production-like setup. For your convenience, you may [run our Docker Compose setup for production locally](../deployment/docker-compose.md#running-the-docker-compose-setup-for-production-locally).