From 1380bd7272efc9f85c6c81166090becdfcced4a6 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Tue, 22 Dec 2020 17:25:36 +0100 Subject: [PATCH 1/3] Very rough sketch of better testing docs --- best_practices.rst | 5 +- form/unit_testing.rst | 6 +- testing.rst | 850 +++++++++++++++++++----------------------- testing/database.rst | 115 ------ 4 files changed, 391 insertions(+), 585 deletions(-) diff --git a/best_practices.rst b/best_practices.rst index 2880c1014e5..8917b619106 100644 --- a/best_practices.rst +++ b/best_practices.rst @@ -399,8 +399,8 @@ Smoke Test your URLs In software engineering, `smoke testing`_ consists of *"preliminary testing to reveal simple failures severe enough to reject a prospective software release"*. -Using :ref:`PHPUnit data providers ` you can define a -functional test that checks that all application URLs load successfully:: +Using `PHPUnit data providers`_ you can define a functional test that +checks that all application URLs load successfully:: // tests/ApplicationAvailabilityFunctionalTest.php namespace App\Tests; @@ -454,3 +454,4 @@ you must set up a redirection. .. _`feature toggles`: https://en.wikipedia.org/wiki/Feature_toggle .. _`smoke testing`: https://en.wikipedia.org/wiki/Smoke_testing_(software) .. _`Webpack`: https://webpack.js.org/ +.. _`PHPUnit data providers`: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html#data-providers diff --git a/form/unit_testing.rst b/form/unit_testing.rst index 795fd55c9ea..33ef7bd4ca4 100644 --- a/form/unit_testing.rst +++ b/form/unit_testing.rst @@ -121,8 +121,8 @@ variable exists and will be available in your form themes:: .. tip:: - Use :ref:`PHPUnit data providers ` to test multiple - form conditions using the same test code. + Use `PHPUnit data providers`_ to test multiple form conditions using + the same test code. .. caution:: @@ -242,3 +242,5 @@ guessers using the :method:`Symfony\\Component\\Form\\Test\\FormIntegrationTestC :method:`Symfony\\Component\\Form\\Test\\FormIntegrationTestCase::getTypeExtensions` and :method:`Symfony\\Component\\Form\\Test\\FormIntegrationTestCase::getTypeGuessers` methods. + +.. _`PHPUnit data providers`: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html#data-providers diff --git a/testing.rst b/testing.rst index 12bd6c74524..736e7d5b880 100644 --- a/testing.rst +++ b/testing.rst @@ -38,10 +38,9 @@ your app): it again. Another solution is to remove the project's ``symfony.lock`` file and run ``composer install`` to force the execution of all Symfony Flex recipes. -Each test - whether it's a unit test or a functional test - is a PHP class -that should live in the ``tests/`` directory of your application. If you follow -this rule, then you can run all of your application's tests with the same -command as before. +Each test is a PHP class that should live in the ``tests/`` directory of +your application. If you follow this rule, then you can run all of your +application's tests with the same command as before. PHPUnit is configured by the ``phpunit.xml.dist`` file in the root of your Symfony application. @@ -51,79 +50,118 @@ Symfony application. Use the ``--coverage-*`` command options to generate code coverage reports. Read the PHPUnit manual to learn more about `code coverage analysis`_. -.. index:: - single: Tests; Unit tests +Types of Tests +-------------- + +`Unit Tests`_ + These tests ensure that *individual* units of source code (e.g. a single + class) behave as intended. + +`Integration Tests`_ + These tests test a combination of classes and commonly interact with + Symfony's service container. These tests do not yet cover the full + working application, those are called *Functional tests*. + +`Functional Tests`_ + Functional tests test the behavior of a complete application. They + make HTTP requests and test that the response is as expected. + +`End to End Tests (E2E)`_ + At last, end to end tests test the application as a real user. They use + a real browser and real integrations with external services. Unit Tests ---------- -A `unit test`_ ensures that individual units of source code (e.g. a single class -or some specific method in some class) meet their design and behave as intended. -If you want to test an entire feature of your application (e.g. registering as a -user or generating an invoice), see the section about :ref:`Functional Tests `. +A `unit test`_ ensures that individual units of source code (e.g. a single +class or some specific method in some class) meet their design and behave +as intended. Writing Symfony unit tests is no different from writing +standard PHPUnit unit tests. You can learn about it in the PHPUnit +documentation: `Writing Tests for PHPUnit`_. -Writing Symfony unit tests is no different from writing standard PHPUnit -unit tests. Suppose, for example, that you have a class called ``Calculator`` -in the ``src/Util/`` directory of the app:: +By convention, the ``tests/`` directory should replicate the directory +of your application for unit tests. So, if you're testing a class in the +``src/Util/`` directory, put the test in the ``tests/Util/`` directory. +Autoloading is automatically enabled via the ``vendor/autoload.php`` file +(as configured by default in the ``phpunit.xml.dist`` file). - // src/Util/Calculator.php - namespace App\Util; +You can run tests using the ``bin/phpunit`` command: - class Calculator - { - public function add($a, $b) - { - return $a + $b; - } - } +.. code-block:: terminal -To test this, create a ``CalculatorTest`` file in the ``tests/Util`` directory -of your application:: + # run all tests of the application + $ php bin/phpunit - // tests/Util/CalculatorTest.php - namespace App\Tests\Util; + # run all tests in the Util/ directory + $ php bin/phpunit tests/Util - use App\Util\Calculator; - use PHPUnit\Framework\TestCase; + # run tests for the Calculator class + $ php bin/phpunit tests/Util/CalculatorTest.php + +Integration Tests +----------------- + +TODO: KernelTestCase + +Accessing the Container +~~~~~~~~~~~~~~~~~~~~~~~ - class CalculatorTest extends TestCase +You can get the same container used in the application, which only includes +the public services:: + + public function testSomething() { - public function testAdd() - { - $calculator = new Calculator(); - $result = $calculator->add(30, 12); + $kernel = self::bootKernel(); + $container = $kernel->getContainer(); + $someService = $container->get('the-service-ID'); - // assert that your calculator added the numbers correctly! - $this->assertEquals(42, $result); - } + // ... } -.. note:: +Symfony tests also have access to a special container that includes both the +public services and the non-removed :ref:`private services ` +services:: - By convention, the ``tests/`` directory should replicate the directory - of your application for unit tests. So, if you're testing a class in the - ``src/Util/`` directory, put the test in the ``tests/Util/`` - directory. + public function testSomething() + { + // this call is needed; otherwise the container will be empty + self::bootKernel(); -Like in your real application - autoloading is automatically enabled via the -``vendor/autoload.php`` file (as configured by default in the -``phpunit.xml.dist`` file). + $container = self::$container; + // $someService = $container->get('the-service-ID'); -You can also limit a test run to a directory or a specific test file: + // ... + } -.. code-block:: terminal +.. caution:: - # run all tests of the application - $ php bin/phpunit + The special container ``test.service_container`` doesn't give access to the + private services that have been removed (those who are not used by any + other services). The solution is to declare those private services as public + in the ``config/services_test.yaml`` file. - # run all tests in the Util/ directory - $ php bin/phpunit tests/Util +.. TODO is this really different from self::$container and how to access + this in KernelTestCase? - # run tests for the Calculator class - $ php bin/phpunit tests/Util/CalculatorTest.php + Finally, for the most rare edge-cases, Symfony includes a special container + which provides access to all services, public and private. This special + container is a service that can be get via the normal container:: -.. index:: - single: Tests; Functional tests + public function testSomething() + { + $client = self::createClient(); + $normalContainer = $client->getContainer(); + $specialContainer = $normalContainer->get('test.service_container'); + + // $somePrivateService = $specialContainer->get('the-service-ID'); + + // ... + } + +Mocking Services +~~~~~~~~~~~~~~~~ + +TODO .. _functional-tests: @@ -139,249 +177,313 @@ tests as far as PHPUnit is concerned, but they have a very specific workflow: * Test the response; * Rinse and repeat. -Before creating your first test, install these packages that provide some of the -utilities used in the functional tests: +Before creating your first test, install the ``symfony/test-pack`` which +requires multiple packages providing some of the utilities used in the +tests: .. code-block:: terminal - $ composer require --dev symfony/browser-kit symfony/css-selector + $ composer require --dev symfony/test-pack -Your First Functional Test -~~~~~~~~~~~~~~~~~~~~~~~~~~ -Functional tests are PHP files that typically live in the ``tests/Controller`` -directory of your application. If you want to test the pages handled by your -``PostController`` class, start by creating a new ``PostControllerTest.php`` -file that extends a special ``WebTestCase`` class. +Set-up your Test Environment +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -As an example, a test could look like this:: +The Client used by functional tests creates a Kernel that runs in a special +``test`` environment. Since Symfony loads the ``config/packages/test/*.yaml`` +in the ``test`` environment, you can tweak any of your application's settings +specifically for testing. - // tests/Controller/PostControllerTest.php - namespace App\Tests\Controller; +For example, by default, the Swift Mailer is configured to *not* actually +deliver emails in the ``test`` environment. You can see this under the ``swiftmailer`` +configuration option: - use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +.. configuration-block:: - class PostControllerTest extends WebTestCase - { - public function testShowPost() - { - $client = static::createClient(); + .. code-block:: yaml - $client->request('GET', '/post/hello-world'); + # config/packages/test/swiftmailer.yaml - $this->assertEquals(200, $client->getResponse()->getStatusCode()); - } - } + # ... + swiftmailer: + disable_delivery: true -.. tip:: + .. code-block:: xml - To run your functional tests, the ``WebTestCase`` class needs to know which - is the application kernel to bootstrap it. The kernel class is usually - defined in the ``KERNEL_CLASS`` environment variable (included in the - default ``.env.test`` file provided by Symfony): + + + - If your use case is more complex, you can also override the - ``createKernel()`` or ``getKernelClass()`` methods of your functional test, - which take precedence over the ``KERNEL_CLASS`` env var. + + + -In the above example, you validated that the HTTP response was successful. The -next step is to validate that the page actually contains the expected content. -The ``createClient()`` method returns a client, which is like a browser that -you'll use to crawl your site:: + .. code-block:: php - $crawler = $client->request('GET', '/post/hello-world'); + // config/packages/test/swiftmailer.php -The ``request()`` method (read -:ref:`more about the request method `) -returns a :class:`Symfony\\Component\\DomCrawler\\Crawler` object which can -be used to select elements in the response, click on links and submit forms. + // ... + $container->loadFromExtension('swiftmailer', [ + 'disable_delivery' => true, + ]); + +You can also use a different environment entirely, or override the default +debug mode (``true``) by passing each as options to the ``createClient()`` +method:: + + $client = static::createClient([ + 'environment' => 'my_test_env', + 'debug' => false, + ]); .. tip:: - The ``Crawler`` only works when the response is an XML or an HTML document. - To get the raw content response, call ``$client->getResponse()->getContent()``. + It is recommended to run your test with ``debug`` set to ``false`` on + your CI server, as it significantly improves test performance. -The crawler integrates with the ``symfony/css-selector`` component to give you the -power of CSS selectors to find content in a page. To install the CSS selector -component, run: +Customizing Environment Variables +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. code-block:: terminal +If you need to customize some environment variables for your tests (e.g. the +``DATABASE_URL`` used by Doctrine), you can do that by overriding anything you +need in your ``.env.test`` file: - $ composer require --dev symfony/css-selector +.. code-block:: text -Now you can use CSS selectors with the crawler. To assert that the phrase -"Hello World" is present in the page's main title, you can use this assertion:: + # .env.test + DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name_test?serverVersion=5.7" - $this->assertSelectorTextContains('html h1.title', 'Hello World'); + # use SQLITE + # DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db" -This assertion checks if the first element matching the CSS selector contains -the given text. This assert calls ``$crawler->filter('html h1.title')`` -internally, which allows you to use CSS selectors to filter any HTML element in -the page and check for its existence, attributes, text, etc. +This file is automatically read in the ``test`` environment: any keys here override +the defaults in ``.env``. -The ``assertSelectorTextContains`` method is not a native PHPUnit assertion and is -available thanks to the ``WebTestCase`` class. +Configuring a Database for Tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 4.3 +Tests that interact with the database should use their own separate database to +not mess with the databases used in the other :ref:`configuration environments `. +To do that, edit or create the ``.env.test.local`` file at the root directory of +your project and define the new value for the ``DATABASE_URL`` env var: - The ``WebTestCase`` assertions were introduced in Symfony 4.3 +.. code-block:: bash -The crawler can also be used to interact with the page. Click on a link by first -selecting it with the crawler using either an XPath expression or a CSS selector, -then use the client to click on it:: + # .env.test.local + DATABASE_URL="mysql://USERNAME:PASSWORD@127.0.0.1:3306/DB_NAME?serverVersion=5.7" - $link = $crawler - ->filter('a:contains("Greet")') // find all links with the text "Greet" - ->eq(1) // select the second link in the list - ->link() - ; +.. tip:: - // and click it - $crawler = $client->click($link); + A common practice is to append the ``_test`` suffix to the original database + names in tests. If the database name in production is called ``project_acme`` + the name of the testing database could be ``project_acme_test``. -Submitting a form is very similar: select a form button, optionally override -some form values and submit the corresponding form:: +The above assumes that each developer/machine uses a different database for the +tests. If the entire team uses the same settings for tests, edit or create the +``.env.test`` file instead and commit it to the shared repository. Learn more +about :ref:`using multiple .env files in Symfony applications `. - $form = $crawler->selectButton('submit')->form(); +Resetting the Database Automatically Before each Test +..................................................... - // set some values - $form['name'] = 'Lucas'; - $form['form_name[subject]'] = 'Hey there!'; +Tests should be independent from each other to avoid side effects. For example, +if some test modifies the database (by adding or removing an entity) it could +change the results of other tests. Run the following command to install a bundle +that ensures that each test is run with the same unmodified database: - // submit the form - $crawler = $client->submit($form); +.. code-block:: terminal -.. tip:: + $ composer require --dev dama/doctrine-test-bundle - The form can also handle uploads and contains methods to fill in different types - of form fields (e.g. ``select()`` and ``tick()``). For details, see the - `Forms`_ section below. +Now, enable it as a PHPUnit extension or listener: -Now that you can navigate through an application, use assertions to test -that it actually does what you expect it to. Use the Crawler to make assertions -on the DOM:: +.. code-block:: xml - // asserts that the response matches a given CSS selector. - $this->assertGreaterThan(0, $crawler->filter('h1')->count()); + + + -Or test against the response content directly if you just want to assert that -the content contains some text or in case that the response is not an XML/HTML -document:: + + + + - $this->assertContains( - 'Hello World', - $client->getResponse()->getContent() - ); +This bundle uses a clever trick to avoid side effects without sacrificing +performance: it begins a database transaction before every test and rolls it +back automatically after the test finishes to undo all changes. Read more in the +documentation of the `DAMADoctrineTestBundle`_. -.. tip:: +.. _doctrine-fixtures: - Instead of installing each testing dependency individually, you can use the - ``test`` :ref:`Symfony pack ` to install all those dependencies at once: +Load Dummy Data Fixtures +........................ - .. code-block:: terminal +Instead of using the real data from the production database, it's common to use +fake or dummy data in the test database. This is usually called *"fixtures data"* +and Doctrine provides a library to create and load them. Install it with: - $ composer require --dev symfony/test-pack +.. code-block:: terminal -.. index:: - single: Tests; Assertions + $ composer require --dev doctrine/doctrine-fixtures-bundle -.. sidebar:: Useful Assertions +Then, use the ``make:fixtures`` command to generate an empty fixture class: - To get you started faster, here is a list of the most common and - useful test assertions:: +.. code-block:: terminal - use Symfony\Component\HttpFoundation\Response; + $ php bin/console make:fixtures - // ... + The class name of the fixtures to create (e.g. AppFixtures): + > ProductFixture - // asserts that there is at least one h2 tag with the class "subtitle" - // the third argument is an optional message shown on failed tests - $this->assertGreaterThan(0, $crawler->filter('h2.subtitle')->count(), - 'There is at least one subtitle' - ); +Customize the new class to load ``Product`` objects into Doctrine:: - // asserts that there are exactly 4 h2 tags on the page - $this->assertCount(4, $crawler->filter('h2')); - - // asserts that the "Content-Type" header is "application/json" - $this->assertResponseHeaderSame('Content-Type', 'application/json'); - // equivalent to: - $this->assertTrue($client->getResponse()->headers->contains( - 'Content-Type', 'application/json' - )); - - // asserts that the response content contains a string - $this->assertContains('foo', $client->getResponse()->getContent()); - // ...or matches a regex - $this->assertRegExp('/foo(bar)?/', $client->getResponse()->getContent()); - - // asserts that the response status code is 2xx - $this->assertResponseIsSuccessful(); - // equivalent to: - $this->assertTrue($client->getResponse()->isSuccessful()); - - // asserts that the response status code is 404 Not Found - $this->assertTrue($client->getResponse()->isNotFound()); - - // asserts a specific status code - $this->assertResponseStatusCodeSame(201); - // HTTP status numbers are available as constants too: - // e.g. 201 === Symfony\Component\HttpFoundation\Response::HTTP_CREATED - // equivalent to: - $this->assertEquals(201, $client->getResponse()->getStatusCode()); - - // asserts that the response is a redirect to /demo/contact - $this->assertResponseRedirects('/demo/contact'); - // equivalent to: - $this->assertTrue($client->getResponse()->isRedirect('/demo/contact')); - // ...or check that the response is a redirect to any URL - $this->assertResponseRedirects(); + // src/DataFixtures/ProductFixture.php + namespace App\DataFixtures; -.. versionadded:: 4.3 + use App\Entity\Product; + use Doctrine\Bundle\FixturesBundle\Fixture; + use Doctrine\Persistence\ObjectManager; - The ``assertResponseHeaderSame()``, ``assertResponseIsSuccessful()``, - ``assertResponseStatusCodeSame()``, ``assertResponseRedirects()`` and other - related methods were introduced in Symfony 4.3. + class ProductFixture extends Fixture + { + public function load(ObjectManager $manager) + { + $product = new Product(); + $product->setName('Priceless widget'); + $product->setPrice(14.50); + $product->setDescription('Ok, I guess it *does* have a price'); + $manager->persist($product); + + // add more products -.. _testing-data-providers: + $manager->flush(); + } + } -Testing against Different Sets of Data --------------------------------------- +Empty the database and reload *all* the fixture classes with: -It's common to have to execute the same test against different sets of data to -check the multiple conditions code must handle. This is solved with PHPUnit's -`data providers`_, which work both for unit and functional tests. +.. code-block:: terminal -First, add one or more arguments to your test method and use them inside the -test code. Then, define another method which returns a nested array with the -arguments to use on each test run. Lastly, add the ``@dataProvider`` annotation -to associate both methods:: + $ php bin/console doctrine:fixtures:load - /** - * @dataProvider provideUrls - */ - public function testPageIsSuccessful($url) - { - $client = self::createClient(); - $client->request('GET', $url); +For more information, read the `DoctrineFixturesBundle documentation`_. - $this->assertTrue($client->getResponse()->isSuccessful()); - } +Write Your First Functional Test +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - public function provideUrls() +Functional tests are PHP files that typically live in the ``tests/Controller`` +directory of your application. If you want to test the pages handled by your +``PostController`` class, start by creating a new ``PostControllerTest.php`` +file that extends a special ``WebTestCase`` class. + +As an example, a test could look like this:: + + // tests/Controller/PostControllerTest.php + namespace App\Tests\Controller; + + use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + + class PostControllerTest extends WebTestCase { - return [ - ['/'], - ['/blog'], - ['/contact'], - // ... - ]; + public function testShowPost() + { + $client = static::createClient(); + + $client->request('GET', '/post/hello-world'); + + $this->assertEquals(200, $client->getResponse()->getStatusCode()); + } } -.. index:: - single: Tests; Client +.. tip:: + + To run your functional tests, the ``WebTestCase`` class needs to know which + is the application kernel to bootstrap it. The kernel class is usually + defined in the ``KERNEL_CLASS`` environment variable (included in the + default ``.env.test`` file provided by Symfony): + + If your use case is more complex, you can also override the + ``createKernel()`` or ``getKernelClass()`` methods of your functional test, + which take precedence over the ``KERNEL_CLASS`` env var. + +In the above example, you validated that the HTTP response was successful. The +next step is to validate that the page actually contains the expected content. +The ``createClient()`` method returns a client, which is like a browser that +you'll use to crawl your site:: + + $crawler = $client->request('GET', '/post/hello-world'); + +The ``request()`` method (read +:ref:`more about the request method `) +returns a :class:`Symfony\\Component\\DomCrawler\\Crawler` object which can +be used to select elements in the response, click on links and submit forms. + +Useful Assertions +~~~~~~~~~~~~~~~~~ + +To get you started faster, here is a list of the most common and +useful test assertions:: + + use Symfony\Component\HttpFoundation\Response; + + // ... + + // asserts that there is at least one h2 tag with the class "subtitle" + // the third argument is an optional message shown on failed tests + $this->assertGreaterThan(0, $crawler->filter('h2.subtitle')->count(), + 'There is at least one subtitle' + ); + + // asserts that there are exactly 4 h2 tags on the page + $this->assertCount(4, $crawler->filter('h2')); + + // asserts that the "Content-Type" header is "application/json" + $this->assertResponseHeaderSame('Content-Type', 'application/json'); + // equivalent to: + $this->assertTrue($client->getResponse()->headers->contains( + 'Content-Type', 'application/json' + )); + + // asserts that the response content contains a string + $this->assertContains('foo', $client->getResponse()->getContent()); + // ...or matches a regex + $this->assertRegExp('/foo(bar)?/', $client->getResponse()->getContent()); + + // asserts that the response status code is 2xx + $this->assertResponseIsSuccessful(); + // equivalent to: + $this->assertTrue($client->getResponse()->isSuccessful()); + + // asserts that the response status code is 404 Not Found + $this->assertTrue($client->getResponse()->isNotFound()); + + // asserts a specific status code + $this->assertResponseStatusCodeSame(201); + // HTTP status numbers are available as constants too: + // e.g. 201 === Symfony\Component\HttpFoundation\Response::HTTP_CREATED + // equivalent to: + $this->assertEquals(201, $client->getResponse()->getStatusCode()); + + // asserts that the response is a redirect to /demo/contact + $this->assertResponseRedirects('/demo/contact'); + // equivalent to: + $this->assertTrue($client->getResponse()->isRedirect('/demo/contact')); + // ...or check that the response is a redirect to any URL + $this->assertResponseRedirects(); + +.. versionadded:: 4.3 + + The ``assertResponseHeaderSame()``, ``assertResponseIsSuccessful()``, + ``assertResponseStatusCodeSame()``, ``assertResponseRedirects()`` and other + related methods were introduced in Symfony 4.3. Working with the Test Client ----------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The test client simulates an HTTP client like a browser and makes requests into your Symfony application:: @@ -489,7 +591,7 @@ script:: $client->insulate(); AJAX Requests -~~~~~~~~~~~~~ +............. The Client provides a :method:`Symfony\\Component\\BrowserKit\\AbstractBrowser::xmlHttpRequest` method, which has the same arguments as the ``request()`` method, and it's a @@ -499,7 +601,7 @@ shortcut to make AJAX requests:: $client->xmlHttpRequest('POST', '/submit', ['name' => 'Fabien']); Browsing -~~~~~~~~ +........ The Client supports many operations that can be done in a real browser:: @@ -516,7 +618,7 @@ The Client supports many operations that can be done in a real browser:: occurred when requesting a URL, as normal browsers do. Accessing Internal Objects -~~~~~~~~~~~~~~~~~~~~~~~~~~ +.......................... If you use the client to test your application, you might want to access the client's internal objects:: @@ -541,69 +643,8 @@ You can also get the objects related to the latest request:: // the Crawler instance $crawler = $client->getCrawler(); -Accessing the Container -~~~~~~~~~~~~~~~~~~~~~~~ - -Functional tests should only test the response (e.g. its contents or its HTTP -status code). However, in some rare circumstances you may need to access the -container to use some service. - -First, you can get the same container used in the application, which only -includes the public services:: - - public function testSomething() - { - $client = self::createClient(); - $container = $client->getContainer(); - // $someService = $container->get('the-service-ID'); - - // ... - } - -Symfony tests also have access to a special container that includes both the -public services and the non-removed :ref:`private services ` -services:: - - public function testSomething() - { - // this call is needed; otherwise the container will be empty - self::bootKernel(); - - $container = self::$container; - // $someService = $container->get('the-service-ID'); - - // ... - } - -Finally, for the most rare edge-cases, Symfony includes a special container -which provides access to all services, public and private. This special -container is a service that can be get via the normal container:: - - public function testSomething() - { - $client = self::createClient(); - $normalContainer = $client->getContainer(); - $specialContainer = $normalContainer->get('test.service_container'); - - // $somePrivateService = $specialContainer->get('the-service-ID'); - - // ... - } - -.. caution:: - - The special container ``test.service_container`` doesn't give access to the - private services that have been removed (those who are not used by any - other services). The solution is to declare those private services as public - in the ``config/services_test.yaml`` file. - -.. tip:: - - If the information you need to check is available from the profiler, use - it instead. - Accessing the Profiler Data -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +........................... On each request, you can enable the Symfony profiler to collect data about the internal handling of that request. For example, the profiler could be used to @@ -624,7 +665,7 @@ For specific details on using the profiler inside a test, see the :doc:`/testing/profiling` article. Redirecting -~~~~~~~~~~~ +........... When a request returns a redirect response, the client does not follow it automatically. You can examine the response and force a redirection @@ -642,8 +683,40 @@ will no longer be followed:: $client->followRedirects(false); +Sending Custom Headers +...................... + +If your application behaves according to some HTTP headers, pass them as the +second argument of ``createClient()``:: + + $client = static::createClient([], [ + 'HTTP_HOST' => 'en.example.com', + 'HTTP_USER_AGENT' => 'MySuperBrowser/1.0', + ]); + +You can also override HTTP headers on a per request basis:: + + $client->request('GET', '/', [], [], [ + 'HTTP_HOST' => 'en.example.com', + 'HTTP_USER_AGENT' => 'MySuperBrowser/1.0', + ]); + +.. caution:: + + The name of your custom headers must follow the syntax defined in the + `section 4.1.18 of RFC 3875`_: replace ``-`` by ``_``, transform it into + uppercase and prefix the result with ``HTTP_``. For example, if your + header name is ``X-Session-Token``, pass ``HTTP_X_SESSION_TOKEN``. + +.. tip:: + + The test client is available as a service in the container in the ``test`` + environment (or wherever the :ref:`framework.test ` + option is enabled). This means you can override the service entirely + if you need to. + Reporting Exceptions -~~~~~~~~~~~~~~~~~~~~ +.................... Debugging exceptions in functional tests may be difficult because by default they are caught and you need to look at the logs to see which exception was @@ -658,13 +731,13 @@ to be reported by PHPUnit:: .. _testing-crawler: The Crawler ------------ +~~~~~~~~~~~ A Crawler instance is returned each time you make a request with the Client. It allows you to traverse HTML documents, select nodes, find links and forms. Traversing -~~~~~~~~~~ +.......... Like jQuery, the Crawler has methods to traverse the DOM of an HTML/XML document. For example, the following finds all ``input[type=submit]`` elements, @@ -720,7 +793,7 @@ narrow down your node selection by chaining the method calls:: ``count($crawler)`` Extracting Information -~~~~~~~~~~~~~~~~~~~~~~ +...................... The Crawler can extract information from the nodes:: @@ -753,7 +826,7 @@ The Crawler can extract information from the nodes:: The option to trim white spaces in ``text()`` was introduced in Symfony 4.4. Links -~~~~~ +..... Use the ``clickLink()`` method to click on the first link that contains the given text (or the first clickable image with that ``alt`` attribute):: @@ -774,7 +847,7 @@ that provides helpful methods specific to links (such as ``getMethod()`` and $client->click($link); Forms -~~~~~ +..... Use the ``submitForm()`` method to submit the form that contains the given button:: @@ -883,171 +956,16 @@ their type:: $client->submit($form, [], ['HTTP_ACCEPT_LANGUAGE' => 'es']); $client->submitForm($button, [], 'POST', ['HTTP_ACCEPT_LANGUAGE' => 'es']); -Adding and Removing Forms to a Collection -......................................... - -If you use a :doc:`Collection of Forms `, -you can't add fields to an existing form with -``$form['task[tags][0][name]'] = 'foo';``. This results in an error -``Unreachable field "…"`` because ``$form`` can only be used in order to -set values of existing fields. In order to add new fields, you have to -add the values to the raw data array:: +End to End Tests (E2E) +---------------------- - // gets the form - $form = $crawler->filter('button')->form(); - - // gets the raw values - $values = $form->getPhpValues(); - - // adds fields to the raw values - $values['task']['tags'][0]['name'] = 'foo'; - $values['task']['tags'][1]['name'] = 'bar'; - - // submits the form with the existing and new values - $crawler = $client->request($form->getMethod(), $form->getUri(), $values, - $form->getPhpFiles()); - - // the 2 tags have been added to the collection - $this->assertEquals(2, $crawler->filter('ul.tags > li')->count()); - -Where ``task[tags][0][name]`` is the name of a field created -with JavaScript. - -You can remove an existing field, e.g. a tag:: - - // gets the values of the form - $values = $form->getPhpValues(); - - // removes the first tag - unset($values['task']['tags'][0]); - - // submits the data - $crawler = $client->request($form->getMethod(), $form->getUri(), - $values, $form->getPhpFiles()); - - // the tag has been removed - $this->assertEquals(0, $crawler->filter('ul.tags > li')->count()); - -.. index:: - pair: Tests; Configuration - -Testing Configuration ---------------------- - -The Client used by functional tests creates a Kernel that runs in a special -``test`` environment. Since Symfony loads the ``config/packages/test/*.yaml`` -in the ``test`` environment, you can tweak any of your application's settings -specifically for testing. - -For example, by default, the Swift Mailer is configured to *not* actually -deliver emails in the ``test`` environment. You can see this under the ``swiftmailer`` -configuration option: - -.. configuration-block:: - - .. code-block:: yaml - - # config/packages/test/swiftmailer.yaml - - # ... - swiftmailer: - disable_delivery: true - - .. code-block:: xml - - - - - - - - - - .. code-block:: php - - // config/packages/test/swiftmailer.php - - // ... - $container->loadFromExtension('swiftmailer', [ - 'disable_delivery' => true, - ]); - -You can also use a different environment entirely, or override the default -debug mode (``true``) by passing each as options to the ``createClient()`` -method:: - - $client = static::createClient([ - 'environment' => 'my_test_env', - 'debug' => false, - ]); - -Customizing Database URL / Environment Variables -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you need to customize some environment variables for your tests (e.g. the -``DATABASE_URL`` used by Doctrine), you can do that by overriding anything you -need in your ``.env.test`` file: - -.. code-block:: text - - # .env.test - DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name_test?serverVersion=5.7" - - # use SQLITE - # DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db" - -This file is automatically read in the ``test`` environment: any keys here override -the defaults in ``.env``. - -.. caution:: - - Applications created before November 2018 had a slightly different system, - involving a ``.env.dist`` file. For information about upgrading, see: - :doc:`configuration/dot-env-changes`. - -Sending Custom Headers -~~~~~~~~~~~~~~~~~~~~~~ - -If your application behaves according to some HTTP headers, pass them as the -second argument of ``createClient()``:: - - $client = static::createClient([], [ - 'HTTP_HOST' => 'en.example.com', - 'HTTP_USER_AGENT' => 'MySuperBrowser/1.0', - ]); - -You can also override HTTP headers on a per request basis:: - - $client->request('GET', '/', [], [], [ - 'HTTP_HOST' => 'en.example.com', - 'HTTP_USER_AGENT' => 'MySuperBrowser/1.0', - ]); - -.. caution:: - - The name of your custom headers must follow the syntax defined in the - `section 4.1.18 of RFC 3875`_: replace ``-`` by ``_``, transform it into - uppercase and prefix the result with ``HTTP_``. For example, if your - header name is ``X-Session-Token``, pass ``HTTP_X_SESSION_TOKEN``. - -.. tip:: - - The test client is available as a service in the container in the ``test`` - environment (or wherever the :ref:`framework.test ` - option is enabled). This means you can override the service entirely - if you need to. - -.. index:: - pair: PHPUnit; Configuration +TODO +* panther +* testing javascript +* UX or form collections as example? PHPUnit Configuration -~~~~~~~~~~~~~~~~~~~~~ +--------------------- Each application has its own PHPUnit configuration, stored in the ``phpunit.xml.dist`` file. You can edit this file to change the defaults or @@ -1127,8 +1045,8 @@ Learn more .. _`PHPUnit`: https://phpunit.de/ .. _`documentation`: https://phpunit.readthedocs.io/ .. _`PHPUnit Bridge component`: https://symfony.com/components/PHPUnit%20Bridge +.. _`Writing Tests for PHPUnit`: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html .. _`unit test`: https://en.wikipedia.org/wiki/Unit_testing .. _`$_SERVER`: https://www.php.net/manual/en/reserved.variables.server.php -.. _`data providers`: https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers .. _`code coverage analysis`: https://phpunit.readthedocs.io/en/9.1/code-coverage-analysis.html .. _`section 4.1.18 of RFC 3875`: https://tools.ietf.org/html/rfc3875#section-4.1.18 diff --git a/testing/database.rst b/testing/database.rst index 7e3acfd1db7..fb6caa0e553 100644 --- a/testing/database.rst +++ b/testing/database.rst @@ -4,121 +4,6 @@ How to Test Code that Interacts with the Database ================================================= -Configuring a Database for Tests --------------------------------- - -Tests that interact with the database should use their own separate database to -not mess with the databases used in the other :ref:`configuration environments `. -To do that, edit or create the ``.env.test.local`` file at the root directory of -your project and define the new value for the ``DATABASE_URL`` env var: - -.. code-block:: bash - - # .env.test.local - DATABASE_URL="mysql://USERNAME:PASSWORD@127.0.0.1:3306/DB_NAME?serverVersion=5.7" - -.. tip:: - - A common practice is to append the ``_test`` suffix to the original database - names in tests. If the database name in production is called ``project_acme`` - the name of the testing database could be ``project_acme_test``. - -The above assumes that each developer/machine uses a different database for the -tests. If the entire team uses the same settings for tests, edit or create the -``.env.test`` file instead and commit it to the shared repository. Learn more -about :ref:`using multiple .env files in Symfony applications `. - -Resetting the Database Automatically Before each Test ------------------------------------------------------ - -Tests should be independent from each other to avoid side effects. For example, -if some test modifies the database (by adding or removing an entity) it could -change the results of other tests. Run the following command to install a bundle -that ensures that each test is run with the same unmodified database: - -.. code-block:: terminal - - $ composer require --dev dama/doctrine-test-bundle - -Now, enable it as a PHPUnit extension or listener: - -.. code-block:: xml - - - - - - - - - - - - - - - - -This bundle uses a clever trick to avoid side effects without sacrificing -performance: it begins a database transaction before every test and rolls it -back automatically after the test finishes to undo all changes. Read more in the -documentation of the `DAMADoctrineTestBundle`_. - -.. _doctrine-fixtures: - -Dummy Data Fixtures -------------------- - -Instead of using the real data from the production database, it's common to use -fake or dummy data in the test database. This is usually called *"fixtures data"* -and Doctrine provides a library to create and load them. Install it with: - -.. code-block:: terminal - - $ composer require --dev doctrine/doctrine-fixtures-bundle - -Then, use the ``make:fixtures`` command to generate an empty fixture class: - -.. code-block:: terminal - - $ php bin/console make:fixtures - - The class name of the fixtures to create (e.g. AppFixtures): - > ProductFixture - -Customize the new class to load ``Product`` objects into Doctrine:: - - // src/DataFixtures/ProductFixture.php - namespace App\DataFixtures; - - use App\Entity\Product; - use Doctrine\Bundle\FixturesBundle\Fixture; - use Doctrine\Persistence\ObjectManager; - - class ProductFixture extends Fixture - { - public function load(ObjectManager $manager) - { - $product = new Product(); - $product->setName('Priceless widget'); - $product->setPrice(14.50); - $product->setDescription('Ok, I guess it *does* have a price'); - $manager->persist($product); - - // add more products - - $manager->flush(); - } - } - -Empty the database and reload *all* the fixture classes with: - -.. code-block:: terminal - - $ php bin/console doctrine:fixtures:load - -For more information, read the `DoctrineFixturesBundle documentation`_. - Mocking a Doctrine Repository in Unit Tests ------------------------------------------- From 42cb8872afc99f2921519224270bd46a8a081cfa Mon Sep 17 00:00:00 2001 From: Nyholm Date: Tue, 2 Mar 2021 17:32:40 +0100 Subject: [PATCH 2/3] Finalize testing document rewrite * Moved around things * Don't mention PHPUnitBridge * Add info about integration tests --- testing.rst | 197 +++++++++++++++++++++------------------------------- 1 file changed, 81 insertions(+), 116 deletions(-) diff --git a/testing.rst b/testing.rst index 736e7d5b880..40bb2f1310c 100644 --- a/testing.rst +++ b/testing.rst @@ -15,44 +15,38 @@ Symfony integrates with an independent library called `PHPUnit`_ to give you a rich testing framework. This article won't cover PHPUnit itself, which has its own excellent `documentation`_. -Before creating your first test, install the `PHPUnit Bridge component`_, which -wraps the original PHPUnit binary to provide additional features: +Before creating your first test, install the PHPUnit with the following command: .. code-block:: terminal - $ composer require --dev symfony/phpunit-bridge + $ composer require --dev phpunit/phpunit -After the library downloads, try executing PHPUnit by running (the first time -you run this, it will download PHPUnit itself and make its classes available in -your app): +After the library is installed, try executing PHPUnit by running: .. code-block:: terminal - $ ./bin/phpunit + $ ./vendor/bin/phpunit .. note:: - The ``./bin/phpunit`` command is created by :ref:`Symfony Flex ` - when installing the ``phpunit-bridge`` package. If the command is missing, you - can remove the package (``composer remove symfony/phpunit-bridge``) and install - it again. Another solution is to remove the project's ``symfony.lock`` file and - run ``composer install`` to force the execution of all Symfony Flex recipes. + :ref:`Symfony Flex ` has automatically created ``phpunit.xml.dist`` + and ``tests/bootstrap.php``. If the files are missing, you can remove the package + (``composer remove phpunit/phpunit``) and install it again. Each test is a PHP class that should live in the ``tests/`` directory of your application. If you follow this rule, then you can run all of your application's tests with the same command as before. -PHPUnit is configured by the ``phpunit.xml.dist`` file in the root of your -Symfony application. - -.. tip:: - - Use the ``--coverage-*`` command options to generate code coverage reports. - Read the PHPUnit manual to learn more about `code coverage analysis`_. +PHPUnit is configured by the ``phpunit.xml.dist`` file in the root of your application. Types of Tests -------------- +To get a common language and shared context, it is important to define a what different +types of tests really mean. Symfony will use the following definition. If you have +learned something different, that is not necessarily wrong. It is just different +from what the Symfony documentation is using. + `Unit Tests`_ These tests ensure that *individual* units of source code (e.g. a single class) behave as intended. @@ -60,22 +54,18 @@ Types of Tests `Integration Tests`_ These tests test a combination of classes and commonly interact with Symfony's service container. These tests do not yet cover the full - working application, those are called *Functional tests*. + working application, those are called *Application tests*. -`Functional Tests`_ - Functional tests test the behavior of a complete application. They +`Application Tests`_ + Application tests test the behavior of a complete application. They make HTTP requests and test that the response is as expected. -`End to End Tests (E2E)`_ - At last, end to end tests test the application as a real user. They use - a real browser and real integrations with external services. - Unit Tests ---------- A `unit test`_ ensures that individual units of source code (e.g. a single class or some specific method in some class) meet their design and behave -as intended. Writing Symfony unit tests is no different from writing +as intended. Writing unit tests is a Symfony application no different from writing standard PHPUnit unit tests. You can learn about it in the PHPUnit documentation: `Writing Tests for PHPUnit`_. @@ -85,52 +75,49 @@ of your application for unit tests. So, if you're testing a class in the Autoloading is automatically enabled via the ``vendor/autoload.php`` file (as configured by default in the ``phpunit.xml.dist`` file). -You can run tests using the ``bin/phpunit`` command: +You can run tests using the ``./vendor/bin/phpunit`` command: .. code-block:: terminal # run all tests of the application - $ php bin/phpunit + $ php ./vendor/bin/phpunit # run all tests in the Util/ directory - $ php bin/phpunit tests/Util + $ php ./vendor/bin/phpunit tests/Util # run tests for the Calculator class - $ php bin/phpunit tests/Util/CalculatorTest.php + $ php ./vendor/bin/phpunit tests/Util/CalculatorTest.php Integration Tests ----------------- -TODO: KernelTestCase - -Accessing the Container -~~~~~~~~~~~~~~~~~~~~~~~ - -You can get the same container used in the application, which only includes -the public services:: - - public function testSomething() - { - $kernel = self::bootKernel(); - $container = $kernel->getContainer(); - $someService = $container->get('the-service-ID'); - - // ... - } +An integration test will test a larger part of your application compared to a unit +test. Integration tests will use the Kernel to fetch a service from the dependency +injection container. -Symfony tests also have access to a special container that includes both the +Symfony tests have access to a special container that includes both the public services and the non-removed :ref:`private services ` services:: - public function testSomething() + // tests/Service/AcmeServiceTest.php + namespace App\Tests\Service; + + use App\Service\AcmeService; + use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; + + class AcmeServiceTest extends KernelTestCase { - // this call is needed; otherwise the container will be empty - self::bootKernel(); + public function testSomething() + { + // this call is needed; otherwise the container will be empty + self::bootKernel(); - $container = self::$container; - // $someService = $container->get('the-service-ID'); + $container = self::$container; + $someService = $container->get(AcmeService::class); - // ... + $result = $someService->something(); + $this->assertTrue($result); + } } .. caution:: @@ -140,55 +127,21 @@ services:: other services). The solution is to declare those private services as public in the ``config/services_test.yaml`` file. -.. TODO is this really different from self::$container and how to access - this in KernelTestCase? - - Finally, for the most rare edge-cases, Symfony includes a special container - which provides access to all services, public and private. This special - container is a service that can be get via the normal container:: - - public function testSomething() - { - $client = self::createClient(); - $normalContainer = $client->getContainer(); - $specialContainer = $normalContainer->get('test.service_container'); - - // $somePrivateService = $specialContainer->get('the-service-ID'); - - // ... - } - -Mocking Services -~~~~~~~~~~~~~~~~ - -TODO - -.. _functional-tests: - -Functional Tests ----------------- - -Functional tests check the integration of the different layers of an -application (from the routing to the views). They are no different from unit -tests as far as PHPUnit is concerned, but they have a very specific workflow: - -* Make a request; -* Click on a link or submit a form; -* Test the response; -* Rinse and repeat. - -Before creating your first test, install the ``symfony/test-pack`` which -requires multiple packages providing some of the utilities used in the -tests: +.. tip:: -.. code-block:: terminal + To run your application tests, the ``KernelTestCase`` class needs to know which + is the application kernel to bootstrap it. The kernel class is usually + defined in the ``KERNEL_CLASS`` environment variable (included in the + default ``.env.test`` file provided by Symfony Flex): - $ composer require --dev symfony/test-pack + If your use case is more complex, you can also override the + ``createKernel()`` or ``getKernelClass()`` methods of your functional test, + which take precedence over the ``KERNEL_CLASS`` env var. Set-up your Test Environment ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The Client used by functional tests creates a Kernel that runs in a special +The tests creates a Kernel that runs in a special ``test`` environment. Since Symfony loads the ``config/packages/test/*.yaml`` in the ``test`` environment, you can tweak any of your application's settings specifically for testing. @@ -236,7 +189,7 @@ You can also use a different environment entirely, or override the default debug mode (``true``) by passing each as options to the ``createClient()`` method:: - $client = static::createClient([ + self::bootKernel([ 'environment' => 'my_test_env', 'debug' => false, ]); @@ -300,7 +253,7 @@ that ensures that each test is run with the same unmodified database: $ composer require --dev dama/doctrine-test-bundle -Now, enable it as a PHPUnit extension or listener: +Now, enable it as a PHPUnit extension: .. code-block:: xml @@ -373,10 +326,34 @@ Empty the database and reload *all* the fixture classes with: For more information, read the `DoctrineFixturesBundle documentation`_. -Write Your First Functional Test -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. _functional-tests: + +Application Tests +----------------- + +Application tests check the integration of all the different layers of the +application (from the routing to the views). They are no different from unit tests +or integration tests as far as PHPUnit is concerned, but they have a very specific +workflow: + +* Make a request; +* Click on a link or submit a form; +* Test the response; +* Rinse and repeat. -Functional tests are PHP files that typically live in the ``tests/Controller`` +Before creating your first test, install the ``symfony/test-pack`` which +requires multiple packages providing some of the utilities used in the +tests: + +.. code-block:: terminal + + $ composer require --dev symfony/test-pack + + +Write Your First Application Test +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Application tests are PHP files that typically live in the ``tests/Controller`` directory of your application. If you want to test the pages handled by your ``PostController`` class, start by creating a new ``PostControllerTest.php`` file that extends a special ``WebTestCase`` class. @@ -400,17 +377,6 @@ As an example, a test could look like this:: } } -.. tip:: - - To run your functional tests, the ``WebTestCase`` class needs to know which - is the application kernel to bootstrap it. The kernel class is usually - defined in the ``KERNEL_CLASS`` environment variable (included in the - default ``.env.test`` file provided by Symfony): - - If your use case is more complex, you can also override the - ``createKernel()`` or ``getKernelClass()`` methods of your functional test, - which take precedence over the ``KERNEL_CLASS`` env var. - In the above example, you validated that the HTTP response was successful. The next step is to validate that the page actually contains the expected content. The ``createClient()`` method returns a client, which is like a browser that @@ -495,7 +461,7 @@ returns a ``Crawler`` instance. .. tip:: - Hardcoding the request URLs is a best practice for functional tests. If the + Hardcoding the request URLs is a best practice for application tests. If the test generates URLs using the Symfony router, it won't detect any change made to the application URLs which may impact the end users. @@ -718,7 +684,7 @@ You can also override HTTP headers on a per request basis:: Reporting Exceptions .................... -Debugging exceptions in functional tests may be difficult because by default +Debugging exceptions in application tests may be difficult because by default they are caught and you need to look at the logs to see which exception was thrown. Disabling catching of exceptions in the test client allows the exception to be reported by PHPUnit:: @@ -1044,7 +1010,6 @@ Learn more .. _`PHPUnit`: https://phpunit.de/ .. _`documentation`: https://phpunit.readthedocs.io/ -.. _`PHPUnit Bridge component`: https://symfony.com/components/PHPUnit%20Bridge .. _`Writing Tests for PHPUnit`: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html .. _`unit test`: https://en.wikipedia.org/wiki/Unit_testing .. _`$_SERVER`: https://www.php.net/manual/en/reserved.variables.server.php From 1091d1356005ebb9f824701fef0decb7e49434ae Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sun, 7 Mar 2021 23:36:21 +0100 Subject: [PATCH 3/3] Finish testing article rewrites --- .doctor-rst.yaml | 1 + testing.rst | 581 +++++++++++++++++++++++++------------------ testing/database.rst | 13 +- 3 files changed, 351 insertions(+), 244 deletions(-) diff --git a/.doctor-rst.yaml b/.doctor-rst.yaml index e71ca735f09..af13fb7be61 100644 --- a/.doctor-rst.yaml +++ b/.doctor-rst.yaml @@ -95,3 +95,4 @@ whitelist: - ".. _`Deploying Symfony 4 Apps on Heroku`: https://devcenter.heroku.com/articles/deploying-symfony4" - '.. versionadded:: 0.2' # MercureBundle - '.. code-block:: twig' + - 'End to End Tests (E2E)' diff --git a/testing.rst b/testing.rst index 40bb2f1310c..095856fb426 100644 --- a/testing.rst +++ b/testing.rst @@ -11,41 +11,45 @@ using both functional and unit tests. The PHPUnit Testing Framework ----------------------------- -Symfony integrates with an independent library called `PHPUnit`_ to give you a -rich testing framework. This article won't cover PHPUnit itself, which has its -own excellent `documentation`_. +Symfony integrates with an independent library called `PHPUnit`_ to give +you a rich testing framework. This article won't cover PHPUnit itself, +which has its own excellent `documentation`_. -Before creating your first test, install the PHPUnit with the following command: +Before creating your first test, install ``phpunit/phpunit`` and the +``symfony/test-pack``, which installs some other packages providing useful +Symfony test utilities: .. code-block:: terminal - $ composer require --dev phpunit/phpunit + $ composer require --dev phpunit/phpunit symfony/test-pack -After the library is installed, try executing PHPUnit by running: +After the library is installed, try running PHPUnit: .. code-block:: terminal - $ ./vendor/bin/phpunit + $ php ./vendor/bin/phpunit -.. note:: +This commands automatically runs your application's tests. Each test is a +PHP class ending with "Test" (e.g. ``BlogControllerTest``) that lives in +the ``tests/`` directory of your application. - :ref:`Symfony Flex ` has automatically created ``phpunit.xml.dist`` - and ``tests/bootstrap.php``. If the files are missing, you can remove the package - (``composer remove phpunit/phpunit``) and install it again. +PHPUnit is configured by the ``phpunit.xml.dist`` file in the root of your +application. -Each test is a PHP class that should live in the ``tests/`` directory of -your application. If you follow this rule, then you can run all of your -application's tests with the same command as before. +.. note:: -PHPUnit is configured by the ``phpunit.xml.dist`` file in the root of your application. + :ref:`Symfony Flex ` automatically creates + ``phpunit.xml.dist`` and ``tests/bootstrap.php``. If these files are + missing, you can try running the recipe again using + ``composer recipes:install phpunit/phpunit --force -v``. Types of Tests -------------- -To get a common language and shared context, it is important to define a what different -types of tests really mean. Symfony will use the following definition. If you have -learned something different, that is not necessarily wrong. It is just different -from what the Symfony documentation is using. +There are many types of automated tests and precise definitions often +differ from project to project. In Symfony, the following definitions are +used. If you have learned something different, that is not necessarily +wrong, just different from what the Symfony documentation is using. `Unit Tests`_ These tests ensure that *individual* units of source code (e.g. a single @@ -53,25 +57,26 @@ from what the Symfony documentation is using. `Integration Tests`_ These tests test a combination of classes and commonly interact with - Symfony's service container. These tests do not yet cover the full + Symfony's service container. These tests do not yet cover the fully working application, those are called *Application tests*. `Application Tests`_ Application tests test the behavior of a complete application. They - make HTTP requests and test that the response is as expected. + make HTTP requests (both real and simulated ones) and test that the + response is as expected. Unit Tests ---------- A `unit test`_ ensures that individual units of source code (e.g. a single class or some specific method in some class) meet their design and behave -as intended. Writing unit tests is a Symfony application no different from writing -standard PHPUnit unit tests. You can learn about it in the PHPUnit -documentation: `Writing Tests for PHPUnit`_. +as intended. Writing unit tests in a Symfony application is no different +from writing standard PHPUnit unit tests. You can learn about it in the +PHPUnit documentation: `Writing Tests for PHPUnit`_. By convention, the ``tests/`` directory should replicate the directory of your application for unit tests. So, if you're testing a class in the -``src/Util/`` directory, put the test in the ``tests/Util/`` directory. +``src/Form/`` directory, put the test in the ``tests/Form/`` directory. Autoloading is automatically enabled via the ``vendor/autoload.php`` file (as configured by default in the ``phpunit.xml.dist`` file). @@ -82,111 +87,109 @@ You can run tests using the ``./vendor/bin/phpunit`` command: # run all tests of the application $ php ./vendor/bin/phpunit - # run all tests in the Util/ directory - $ php ./vendor/bin/phpunit tests/Util + # run all tests in the Form/ directory + $ php ./vendor/bin/phpunit tests/Form + + # run tests for the UserType class + $ php ./vendor/bin/phpunit tests/Form/UserTypeTest.php - # run tests for the Calculator class - $ php ./vendor/bin/phpunit tests/Util/CalculatorTest.php +.. tip:: + + In large test suites, it can make sense to create subdirectories for + each type of tests (e.g. ``tests/Unit/`` and ``test/Functional/``). + +.. _integration-tests: Integration Tests ----------------- -An integration test will test a larger part of your application compared to a unit -test. Integration tests will use the Kernel to fetch a service from the dependency -injection container. +An integration test will test a larger part of your application compared to +a unit test (e.g. a combination of services). Integration tests might want +to use the Symfony Kernel to fetch a service from the dependency injection +container. -Symfony tests have access to a special container that includes both the -public services and the non-removed :ref:`private services ` -services:: +Symfony provides a :class:`Symfony\\Bundle\\FrameworkBundle\\Test\\KernelTestCase` +class to help you creating and booting the kernel in your tests using +``bootKernel()``:: - // tests/Service/AcmeServiceTest.php + // tests/Service/NewsletterGeneratorTest.php namespace App\Tests\Service; - use App\Service\AcmeService; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; - class AcmeServiceTest extends KernelTestCase + class NewsletterGeneratorTest extends KernelTestCase { public function testSomething() { - // this call is needed; otherwise the container will be empty self::bootKernel(); - $container = self::$container; - $someService = $container->get(AcmeService::class); - - $result = $someService->something(); - $this->assertTrue($result); + // ... } } -.. caution:: +The ``KernelTestCase`` also makes sure your kernel is rebooted for each +test. This assures that each test is run independently from eachother. - The special container ``test.service_container`` doesn't give access to the - private services that have been removed (those who are not used by any - other services). The solution is to declare those private services as public - in the ``config/services_test.yaml`` file. +To run your application tests, the ``KernelTestCase`` class needs to +find the application kernel to initialize. The kernel class is +usually defined in the ``KERNEL_CLASS`` environment variable +(included in the default ``.env.test`` file provided by Symfony Flex): -.. tip:: +.. code-block:: env + + # .env.test + KERNEL_CLASS=App\Kernel - To run your application tests, the ``KernelTestCase`` class needs to know which - is the application kernel to bootstrap it. The kernel class is usually - defined in the ``KERNEL_CLASS`` environment variable (included in the - default ``.env.test`` file provided by Symfony Flex): +.. note:: If your use case is more complex, you can also override the - ``createKernel()`` or ``getKernelClass()`` methods of your functional test, - which take precedence over the ``KERNEL_CLASS`` env var. + ``getKernelClass()`` or ``createKernel()`` methods of your functional + test, which take precedence over the ``KERNEL_CLASS`` env var. Set-up your Test Environment ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The tests creates a Kernel that runs in a special -``test`` environment. Since Symfony loads the ``config/packages/test/*.yaml`` -in the ``test`` environment, you can tweak any of your application's settings -specifically for testing. +The tests create a kernel that runs in the ``test`` +:ref:`environment `. This allows to have +special settings for your tests inside ``config/packages/test/``. -For example, by default, the Swift Mailer is configured to *not* actually -deliver emails in the ``test`` environment. You can see this under the ``swiftmailer`` -configuration option: +If you have Symfony Flex installed, some packages already installed some +useful test configuration. For example, by default, the Twig bundle is +configured to be especially strict to catch errors before deploying your +code to production: .. configuration-block:: .. code-block:: yaml - # config/packages/test/swiftmailer.yaml - - # ... - swiftmailer: - disable_delivery: true + # config/packages/test/twig.yaml + twig: + strict_variables: true .. code-block:: xml - + + http://symfony.com/schema/dic/twig + https://symfony.com/schema/dic/twig/twig-1.0.xsd"> - - + .. code-block:: php - // config/packages/test/swiftmailer.php - - // ... - $container->loadFromExtension('swiftmailer', [ - 'disable_delivery' => true, + // config/packages/test/twig.php + $container->loadFromExtension('twig', [ + 'strict_variables' => true, ]); You can also use a different environment entirely, or override the default -debug mode (``true``) by passing each as options to the ``createClient()`` +debug mode (``true``) by passing each as options to the ``bootKernel()`` method:: self::bootKernel([ @@ -197,7 +200,15 @@ method:: .. tip:: It is recommended to run your test with ``debug`` set to ``false`` on - your CI server, as it significantly improves test performance. + your CI server, as it significantly improves test performance. This + disables clearing the cache. If your tests don't run in a clean + environment each time, you have to manually clear it using for instance + this code in ``tests/bootstrap.php``:: + + // ... + + // ensure a fresh cache when debug mode is disabled + (new \Symfony\Component\Filesystem\Filesystem())->remove(__DIR__.'/../var/cache/test'); Customizing Environment Variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -209,45 +220,112 @@ need in your ``.env.test`` file: .. code-block:: text # .env.test + + # ... DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name_test?serverVersion=5.7" - # use SQLITE - # DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db" +In the test environment, these env files are read (if vars are duplicated +in them, files lower in the list override previous items): + +#. ``.env``: containing env vars with application defaults; +#. ``.env.test``: overriding/setting specific test values or vars; +#. ``.env.test.local``: overriding settings specific for this machine. + +.. caution:: + + The ``.env.local`` file is **not** used in the test environment, to + make each test set-up as consistent as possible. + +Retrieving Services in the Test +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In your integration tests, you often need to fetch the service from the +service container to call a specific method. After booting the kernel, +the container is stored in ``self::$container``:: + + // tests/Service/NewsletterGeneratorTest.php + namespace App\Tests\Service; + + use App\Service\NewsletterGenerator; + use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; + + class NewsletterGeneratorTest extends KernelTestCase + { + public function testSomething() + { + // (1) boot the Symfony kernel + self::bootKernel(); + + // (2) use self::$container to access the service container + $container = self::$container; + + // (3) run some service & test the result + $newsletterGenerator = $container->get(NewsletterGenerator::class); + $newsletter = $newsletterGenerator->generateMonthlyNews(...); -This file is automatically read in the ``test`` environment: any keys here override -the defaults in ``.env``. + $this->assertEquals(..., $newsletter->getContent()); + } + } + +The container in ``self::$container`` is actually a special test container. +It gives you access to both the public services and the non-removed +:ref:`private services ` services. + +.. note:: + + If you need to test private services that have been removed (those who + are not used by any other services), you need to declare those private + services as public in the ``config/services_test.yaml`` file. + +.. _testing-databases: Configuring a Database for Tests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Tests that interact with the database should use their own separate database to -not mess with the databases used in the other :ref:`configuration environments `. -To do that, edit or create the ``.env.test.local`` file at the root directory of -your project and define the new value for the ``DATABASE_URL`` env var: +Tests that interact with the database should use their own separate +database to not mess with the databases used in the other +:ref:`configuration environments `. + +To do that, edit or create the ``.env.test.local`` file at the root +directory of your project and define the new value for the ``DATABASE_URL`` +env var: -.. code-block:: bash +.. code-block:: env # .env.test.local DATABASE_URL="mysql://USERNAME:PASSWORD@127.0.0.1:3306/DB_NAME?serverVersion=5.7" -.. tip:: +This assumes that each developer/machine uses a different database for the +tests. If the test set-up is the same on each machine, use the ``.env.test`` +file instead and commit it to the shared repository. Learn more about +:ref:`using multiple .env files in Symfony applications `. + +After that, you can create the test database and all tables using: + +.. code-block:: terminal - A common practice is to append the ``_test`` suffix to the original database - names in tests. If the database name in production is called ``project_acme`` - the name of the testing database could be ``project_acme_test``. + # create the test database + $ php bin/console --env=test doctrine:database:create -The above assumes that each developer/machine uses a different database for the -tests. If the entire team uses the same settings for tests, edit or create the -``.env.test`` file instead and commit it to the shared repository. Learn more -about :ref:`using multiple .env files in Symfony applications `. + # create the tables/columns in the test database + $ php bin/console --env=test doctrine:schema:create + +.. tip:: + + A common practice is to append the ``_test`` suffix to the original + database names in tests. If the database name in production is called + ``project_acme`` the name of the testing database could be + ``project_acme_test``. Resetting the Database Automatically Before each Test ..................................................... -Tests should be independent from each other to avoid side effects. For example, -if some test modifies the database (by adding or removing an entity) it could -change the results of other tests. Run the following command to install a bundle -that ensures that each test is run with the same unmodified database: +Tests should be independent from each other to avoid side effects. For +example, if some test modifies the database (by adding or removing an +entity) it could change the results of other tests. + +The `DAMADoctrineTestBundle`_ uses Doctrine transactions to let each test +interact with an unmodified database. Install it using: .. code-block:: terminal @@ -266,25 +344,27 @@ Now, enable it as a PHPUnit extension: -This bundle uses a clever trick to avoid side effects without sacrificing -performance: it begins a database transaction before every test and rolls it -back automatically after the test finishes to undo all changes. Read more in the -documentation of the `DAMADoctrineTestBundle`_. +That's it! This bundle uses a clever trick: it begins a database +transaction before every test and rolls it back automatically after the +test finishes to undo all changes. Read more in the documentation of the +`DAMADoctrineTestBundle`_. .. _doctrine-fixtures: Load Dummy Data Fixtures ........................ -Instead of using the real data from the production database, it's common to use -fake or dummy data in the test database. This is usually called *"fixtures data"* -and Doctrine provides a library to create and load them. Install it with: +Instead of using the real data from the production database, it's common to +use fake or dummy data in the test database. This is usually called +*"fixtures data"* and Doctrine provides a library to create and load them. +Install it with: .. code-block:: terminal $ composer require --dev doctrine/doctrine-fixtures-bundle -Then, use the ``make:fixtures`` command to generate an empty fixture class: +Then, use the ``make:fixtures`` command of the `SymfonyMakerBundle`_ to +generate an empty fixture class: .. code-block:: terminal @@ -293,7 +373,8 @@ Then, use the ``make:fixtures`` command to generate an empty fixture class: The class name of the fixtures to create (e.g. AppFixtures): > ProductFixture -Customize the new class to load ``Product`` objects into Doctrine:: +Then you modify use this class to load new entities in the database. For +instance, to load ``Product`` objects into Doctrine, use:: // src/DataFixtures/ProductFixture.php namespace App\DataFixtures; @@ -332,33 +413,44 @@ Application Tests ----------------- Application tests check the integration of all the different layers of the -application (from the routing to the views). They are no different from unit tests -or integration tests as far as PHPUnit is concerned, but they have a very specific -workflow: +application (from the routing to the views). They are no different from +unit tests or integration tests as far as PHPUnit is concerned, but they +have a very specific workflow: -* Make a request; -* Click on a link or submit a form; -* Test the response; -* Rinse and repeat. +#. Make a request; +#. Click on a link or submit a form; +#. Test the response; +#. Rinse and repeat. -Before creating your first test, install the ``symfony/test-pack`` which -requires multiple packages providing some of the utilities used in the -tests: - -.. code-block:: terminal - - $ composer require --dev symfony/test-pack +.. note:: + The tools used in this section can be installed via the ``symfony/test-pack``, + use ``composer require symfony/test-pack`` if you haven't done so already. Write Your First Application Test ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Application tests are PHP files that typically live in the ``tests/Controller`` -directory of your application. If you want to test the pages handled by your -``PostController`` class, start by creating a new ``PostControllerTest.php`` -file that extends a special ``WebTestCase`` class. +Application tests are PHP files that typically live in the ``tests/Controller/`` +directory of your application. They often extend +:class:`Symfony\\Bundle\\FrameworkBundle\\Test\\WebTestCase`. This class +adds special logic on top of the ``KernelTestCase``. You can read more +about that in the above :ref:`section on integration tests `. + +If you want to test the pages handled by your +``PostController`` class, start by creating a new ``PostControllerTest`` +using the ``make:test`` command of the `SymfonyMakerBundle`_: + +.. code-block:: terminal + + $ php bin/console make:test + + Which test type would you like?: + > WebTestCase -As an example, a test could look like this:: + The name of the test class (e.g. BlogPostTest): + > Controller\PostControllerTest + +This creates the following test class:: // tests/Controller/PostControllerTest.php namespace App\Tests\Controller; @@ -367,87 +459,36 @@ As an example, a test could look like this:: class PostControllerTest extends WebTestCase { - public function testShowPost() + public function testSomething(): void { + // This calls KernelTestCase::bootKernel(), and creates a + // "client" that is acting as the browser $client = static::createClient(); - $client->request('GET', '/post/hello-world'); + // Request a specific page + $crawler = $client->request('GET', '/'); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); + // Validate a successful response and some content + $this->assertResponseIsSuccessful(); + $this->assertSelectorTextContains('h1', 'Hello World'); } } -In the above example, you validated that the HTTP response was successful. The -next step is to validate that the page actually contains the expected content. -The ``createClient()`` method returns a client, which is like a browser that -you'll use to crawl your site:: +In the above example, the test validates that the HTTP response was successful +and the request body contains a ``

`` tag with ``"Hello world"``. The +``createClient()`` method also returns a client, which is like a browser +that you can use to crawl your site:: $crawler = $client->request('GET', '/post/hello-world'); + // for instance, count the number of ``.comment`` elements on the page + $this->assertCount(4, $crawler->filter('.comment')); + The ``request()`` method (read :ref:`more about the request method `) returns a :class:`Symfony\\Component\\DomCrawler\\Crawler` object which can be used to select elements in the response, click on links and submit forms. -Useful Assertions -~~~~~~~~~~~~~~~~~ - -To get you started faster, here is a list of the most common and -useful test assertions:: - - use Symfony\Component\HttpFoundation\Response; - - // ... - - // asserts that there is at least one h2 tag with the class "subtitle" - // the third argument is an optional message shown on failed tests - $this->assertGreaterThan(0, $crawler->filter('h2.subtitle')->count(), - 'There is at least one subtitle' - ); - - // asserts that there are exactly 4 h2 tags on the page - $this->assertCount(4, $crawler->filter('h2')); - - // asserts that the "Content-Type" header is "application/json" - $this->assertResponseHeaderSame('Content-Type', 'application/json'); - // equivalent to: - $this->assertTrue($client->getResponse()->headers->contains( - 'Content-Type', 'application/json' - )); - - // asserts that the response content contains a string - $this->assertContains('foo', $client->getResponse()->getContent()); - // ...or matches a regex - $this->assertRegExp('/foo(bar)?/', $client->getResponse()->getContent()); - - // asserts that the response status code is 2xx - $this->assertResponseIsSuccessful(); - // equivalent to: - $this->assertTrue($client->getResponse()->isSuccessful()); - - // asserts that the response status code is 404 Not Found - $this->assertTrue($client->getResponse()->isNotFound()); - - // asserts a specific status code - $this->assertResponseStatusCodeSame(201); - // HTTP status numbers are available as constants too: - // e.g. 201 === Symfony\Component\HttpFoundation\Response::HTTP_CREATED - // equivalent to: - $this->assertEquals(201, $client->getResponse()->getStatusCode()); - - // asserts that the response is a redirect to /demo/contact - $this->assertResponseRedirects('/demo/contact'); - // equivalent to: - $this->assertTrue($client->getResponse()->isRedirect('/demo/contact')); - // ...or check that the response is a redirect to any URL - $this->assertResponseRedirects(); - -.. versionadded:: 4.3 - - The ``assertResponseHeaderSame()``, ``assertResponseIsSuccessful()``, - ``assertResponseStatusCodeSame()``, ``assertResponseRedirects()`` and other - related methods were introduced in Symfony 4.3. - Working with the Test Client ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -461,41 +502,9 @@ returns a ``Crawler`` instance. .. tip:: - Hardcoding the request URLs is a best practice for application tests. If the - test generates URLs using the Symfony router, it won't detect any change - made to the application URLs which may impact the end users. - -.. _testing-request-method-sidebar: - -.. sidebar:: More about the ``request()`` Method: - - The full signature of the ``request()`` method is:: - - request( - $method, - $uri, - array $parameters = [], - array $files = [], - array $server = [], - $content = null, - $changeHistory = true - ) - - The ``server`` array is the raw values that you'd expect to normally - find in the PHP `$_SERVER`_ superglobal. For example, to set the - ``Content-Type`` and ``Referer`` HTTP headers, you'd pass the following (mind - the ``HTTP_`` prefix for non standard headers):: - - $client->request( - 'GET', - '/post/hello-world', - [], - [], - [ - 'CONTENT_TYPE' => 'application/json', - 'HTTP_REFERER' => '/foo/bar', - ] - ); + Hardcoding the request URLs is a best practice for application tests. + If the test generates URLs using the Symfony router, it won't detect + any change made to the application URLs which may impact the end users. Use the crawler to find DOM elements in the response. These elements can then be used to click on links and submit forms:: @@ -556,6 +565,38 @@ script:: $client->insulate(); +.. _testing-request-method-sidebar: + +.. sidebar:: More about the ``request()`` Method: + + The full signature of the ``request()`` method is:: + + request( + $method, + $uri, + array $parameters = [], + array $files = [], + array $server = [], + $content = null, + $changeHistory = true + ) + + The ``server`` array is the raw values that you'd expect to normally + find in the PHP `$_SERVER`_ superglobal. For example, to set the + ``Content-Type`` and ``Referer`` HTTP headers, you'd pass the following (mind + the ``HTTP_`` prefix for non standard headers):: + + $client->request( + 'GET', + '/post/hello-world', + [], + [], + [ + 'CONTENT_TYPE' => 'application/json', + 'HTTP_REFERER' => '/foo/bar', + ] + ); + AJAX Requests ............. @@ -691,6 +732,65 @@ to be reported by PHPUnit:: $client->catchExceptions(false); +Useful Assertions +~~~~~~~~~~~~~~~~~ + +To get you started faster, here is a list of the most common and +useful test assertions:: + + use Symfony\Component\HttpFoundation\Response; + + // ... + + // asserts that there is at least one h2 tag with the class "subtitle" + // the third argument is an optional message shown on failed tests + $this->assertGreaterThan(0, $crawler->filter('h2.subtitle')->count(), + 'There is at least one subtitle' + ); + + // asserts that there are exactly 4 h2 tags on the page + $this->assertCount(4, $crawler->filter('h2')); + + // asserts that the "Content-Type" header is "application/json" + $this->assertResponseHeaderSame('Content-Type', 'application/json'); + // equivalent to: + $this->assertTrue($client->getResponse()->headers->contains( + 'Content-Type', 'application/json' + )); + + // asserts that the response content contains a string + $this->assertContains('foo', $client->getResponse()->getContent()); + // ...or matches a regex + $this->assertRegExp('/foo(bar)?/', $client->getResponse()->getContent()); + + // asserts that the response status code is 2xx + $this->assertResponseIsSuccessful(); + // equivalent to: + $this->assertTrue($client->getResponse()->isSuccessful()); + + // asserts that the response status code is 404 Not Found + $this->assertTrue($client->getResponse()->isNotFound()); + + // asserts a specific status code + $this->assertResponseStatusCodeSame(201); + // HTTP status numbers are available as constants too: + // e.g. 201 === Symfony\Component\HttpFoundation\Response::HTTP_CREATED + // equivalent to: + $this->assertEquals(201, $client->getResponse()->getStatusCode()); + + // asserts that the response is a redirect to /demo/contact + $this->assertResponseRedirects('/demo/contact'); + // equivalent to: + $this->assertTrue($client->getResponse()->isRedirect('/demo/contact')); + // ...or check that the response is a redirect to any URL + $this->assertResponseRedirects(); + +.. versionadded:: 4.3 + + The ``assertResponseHeaderSame()``, ``assertResponseIsSuccessful()``, + ``assertResponseStatusCodeSame()``, ``assertResponseRedirects()`` and other + related methods were introduced in Symfony 4.3. + .. index:: single: Tests; Crawler @@ -922,13 +1022,13 @@ their type:: $client->submit($form, [], ['HTTP_ACCEPT_LANGUAGE' => 'es']); $client->submitForm($button, [], 'POST', ['HTTP_ACCEPT_LANGUAGE' => 'es']); -End to End Tests (E2E) ----------------------- +.. TODO + End to End Tests (E2E) + ---------------------- -TODO -* panther -* testing javascript -* UX or form collections as example? + * panther + * testing javascript + * UX or form collections as example? PHPUnit Configuration --------------------- @@ -976,8 +1076,8 @@ configuration adds tests from a custom ``lib/tests`` directory: -To include other directories in the code coverage, also edit the ```` -section: +To include other directories in the `code coverage analysis`_, also edit the +```` section: .. code-block:: xml @@ -1012,6 +1112,9 @@ Learn more .. _`documentation`: https://phpunit.readthedocs.io/ .. _`Writing Tests for PHPUnit`: https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html .. _`unit test`: https://en.wikipedia.org/wiki/Unit_testing +.. _`DAMADoctrineTestBundle`: https://github.com/dmaicher/doctrine-test-bundle +.. _`DoctrineFixturesBundle documentation`: https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html .. _`$_SERVER`: https://www.php.net/manual/en/reserved.variables.server.php +.. _`SymfonyMakerBundle`: https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html .. _`code coverage analysis`: https://phpunit.readthedocs.io/en/9.1/code-coverage-analysis.html .. _`section 4.1.18 of RFC 3875`: https://tools.ietf.org/html/rfc3875#section-4.1.18 diff --git a/testing/database.rst b/testing/database.rst index fb6caa0e553..ec9a25c00c2 100644 --- a/testing/database.rst +++ b/testing/database.rst @@ -1,8 +1,14 @@ .. index:: single: Tests; Database -How to Test Code that Interacts with the Database -================================================= +How to Test A Doctrine Repository +================================= + +.. seealso:: + + The :ref:`main Testing guide ` describes how to use + and set-up a database for your automated tests. The contents of this + article show ways to test your Doctrine repositories. Mocking a Doctrine Repository in Unit Tests ------------------------------------------- @@ -134,6 +140,3 @@ so, get the entity manager via the service container as follows:: $this->entityManager = null; } } - -.. _`DAMADoctrineTestBundle`: https://github.com/dmaicher/doctrine-test-bundle -.. _`DoctrineFixturesBundle documentation`: https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html