From 986df8879ac5bf001124aea33ebe4adf75ce3b8b Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 13 Sep 2019 13:26:56 +0200 Subject: [PATCH] [Doctrine] Put all docs about testing databases in a single article --- _build/redirection_map | 1 + configuration.rst | 2 + doctrine.rst | 58 +----------- testing/database.rst | 206 ++++++++++++++++++++++++++++++++--------- testing/doctrine.rst | 66 ------------- 5 files changed, 168 insertions(+), 165 deletions(-) delete mode 100644 testing/doctrine.rst diff --git a/_build/redirection_map b/_build/redirection_map index 5594141967e..3060808842e 100644 --- a/_build/redirection_map +++ b/_build/redirection_map @@ -456,3 +456,4 @@ /templating/namespaced_paths /templates /templating/embedding_controllers /templates /templating/inheritance /templates +/testing/doctrine /testing/database diff --git a/configuration.rst b/configuration.rst index 77e770f0181..d5e23aee150 100644 --- a/configuration.rst +++ b/configuration.rst @@ -602,6 +602,8 @@ operating systems. :doc:`Symfony profiler `. In practice this shouldn't be a problem because the web profiler must **never** be enabled in production. +.. _configuration-multiple-env-files: + Managing Multiple .env Files ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doctrine.rst b/doctrine.rst index 0a50e98ed10..9ffaa964fd9 100644 --- a/doctrine.rst +++ b/doctrine.rst @@ -799,58 +799,10 @@ relationships. For info, see :doc:`/doctrine/associations`. -.. _doctrine-fixtures: +Database Testing +---------------- -Dummy Data Fixtures -------------------- - -Doctrine provides a library that allows you to programmatically load testing -data into your project (i.e. "fixture data"). 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 Doctrine\Bundle\FixturesBundle\Fixture; - use Doctrine\Common\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 information, see the "`DoctrineFixturesBundle`_" documentation. +Read the article about :doc:`testing code that interacts with the database `. Learn more ---------- @@ -870,8 +822,7 @@ Learn more doctrine/mongodb_session_storage doctrine/resolve_target_entity doctrine/reverse_engineering - -* `DoctrineFixturesBundle`_ + testing/database .. _`Doctrine`: http://www.doctrine-project.org/ .. _`RFC 3986`: https://www.ietf.org/rfc/rfc3986.txt @@ -880,7 +831,6 @@ Learn more .. _`Doctrine Query Language`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html .. _`Reserved SQL keywords documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#quoting-reserved-words .. _`DoctrineMongoDBBundle docs`: https://symfony.com/doc/current/bundles/DoctrineMongoDBBundle/index.html -.. _`DoctrineFixturesBundle`: https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html .. _`Transactions and Concurrency`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/transactions-and-concurrency.html .. _`DoctrineMigrationsBundle`: https://github.com/doctrine/DoctrineMigrationsBundle .. _`NativeQuery`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/native-sql.html diff --git a/testing/database.rst b/testing/database.rst index ee48e55d8b1..0233d61aaff 100644 --- a/testing/database.rst +++ b/testing/database.rst @@ -4,40 +4,126 @@ How to Test Code that Interacts with the Database ================================================= -If your code interacts with the database, e.g. reads data from or stores data -into it, you need to adjust your tests to take this into account. There are -many ways to deal with this. In a unit test, you can create a mock for -a ``Repository`` and use it to return expected objects. In a functional test, -you may need to prepare a test database with predefined values to ensure that -your test always has the same data to work with. +Configuring a Database for Tests +-------------------------------- -.. note:: +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: - If you want to test your queries directly, see :doc:`/testing/doctrine`. +.. code-block:: bash + + # .env.test.local + DATABASE_URL=mysql://USERNAME:PASSWORD@127.0.0.1/DB_NAME .. tip:: - A popular technique to improve the performance of tests that interact with - the database is to begin a transaction before every test and roll it back - after the test has finished. This makes it unnecessary to recreate the - database or reload fixtures before every test. A community bundle called - `DoctrineTestBundle`_ provides this feature. + 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``. -Mocking the ``Repository`` in a Unit 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 `. -If you want to test code which depends on a Doctrine repository in isolation, -you need to mock the ``Repository``. Normally you inject the ``EntityManager`` -into your class and use it to get the repository. This makes things a little -more difficult as you need to mock both the ``EntityManager`` and your repository -class. +Resetting the Database Automatically Before each Test +----------------------------------------------------- -.. tip:: +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 scarifying +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: - It is possible (and a good idea) to inject your repository directly by - registering your repository as a :doc:`factory service `. - This is a little bit more work to setup, but makes testing easier as you - only need to mock the repository. +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 Doctrine\Bundle\FixturesBundle\Fixture; + use Doctrine\Common\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 +------------------------------------------- + +**Unit testing Doctrine repositories is not recommended**. Repositories are +meant to be tested against a real database connection. However, in case you +still need to do this, look at the following example. Suppose the class you want to test looks like this:: @@ -66,8 +152,8 @@ Suppose the class you want to test looks like this:: } } -Since the ``EntityManagerInterface`` gets injected into the class through the constructor, -you can pass a mock object within a test:: +Since the ``EntityManagerInterface`` gets injected into the class through the +constructor, you can pass a mock object within a test:: // tests/Salary/SalaryCalculatorTest.php namespace App\Tests\Salary; @@ -95,6 +181,8 @@ you can pass a mock object within a test:: ->willReturn($employee); // Last, mock the EntityManager to return the mock of the repository + // (this is not needed if the class being tested injects the + // repository it uses instead of the entire object manager) $objectManager = $this->createMock(ObjectManager::class); // use getMock() on PHPUnit 5.3 or below // $objectManager = $this->getMock(ObjectManager::class); @@ -112,26 +200,54 @@ the employee which gets returned by the ``Repository``, which itself gets returned by the ``EntityManager``. This way, no real class is involved in testing. -Changing Database Settings for Functional Tests ------------------------------------------------ +Mocking a Doctrine Repository in Functional Tests +------------------------------------------------- -If you have functional tests, you want them to interact with a real database. -Most of the time you want to use a dedicated database connection to make sure -not to overwrite data you entered when developing the application and also -to be able to clear the database before every test. +In :ref:`functional tests ` you'll make queries to the +database using the actual Doctrine repositories, instead of mocking them. To do +so, get the entity manager via the service container as follows:: -To do this, you can override the value of the ``DATABASE_URL`` env var in the -``phpunit.xml.dist`` to use a different database for your tests: + // tests/Repository/ProductRepositoryTest.php + namespace App\Tests\Repository; -.. code-block:: xml + use App\Entity\Product; + use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; - - - - - - - - + class ProductRepositoryTest extends KernelTestCase + { + /** + * @var \Doctrine\ORM\EntityManager + */ + private $entityManager; + + protected function setUp() + { + $kernel = self::bootKernel(); + + $this->entityManager = $kernel->getContainer() + ->get('doctrine') + ->getManager(); + } + + public function testSearchByName() + { + $product = $this->entityManager + ->getRepository(Product::class) + ->findOneBy(['name' => 'Priceless widget']) + ; + + $this->assertSame(14.50, $product->getPrice()); + } + + protected function tearDown() + { + parent::tearDown(); + + // doing this is recommended to avoid memory leaks + $this->entityManager->close(); + $this->entityManager = null; + } + } -.. _`DoctrineTestBundle`: https://github.com/dmaicher/doctrine-test-bundle +.. _`DAMADoctrineTestBundle`: https://github.com/dmaicher/doctrine-test-bundle +.. _`DoctrineFixturesBundle documentation`: https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html diff --git a/testing/doctrine.rst b/testing/doctrine.rst deleted file mode 100644 index dacfaf7c006..00000000000 --- a/testing/doctrine.rst +++ /dev/null @@ -1,66 +0,0 @@ -.. index:: - single: Tests; Doctrine - -How to Test Doctrine Repositories -================================= - -Unit testing Doctrine repositories in a Symfony project is not recommended. -When you're dealing with a repository, you're really dealing with something -that's meant to be tested against a real database connection. - -Fortunately, you can test your queries against a real database, as described -below. - -Functional Testing ------------------- - -If you need to actually execute a query, you will need to boot the kernel -to get a valid connection. In this case, you'll extend the ``KernelTestCase`` -to have the Symfony environment available:: - - // tests/Repository/ProductRepositoryTest.php - namespace App\Tests\Repository; - - use App\Entity\Product; - use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; - - class ProductRepositoryTest extends KernelTestCase - { - /** - * @var \Doctrine\ORM\EntityManager - */ - private $entityManager; - - /** - * {@inheritDoc} - */ - protected function setUp() - { - $kernel = self::bootKernel(); - - $this->entityManager = $kernel->getContainer() - ->get('doctrine') - ->getManager(); - } - - public function testSearchByCategoryName() - { - $products = $this->entityManager - ->getRepository(Product::class) - ->searchByCategoryName('foo') - ; - - $this->assertCount(1, $products); - } - - /** - * {@inheritDoc} - */ - protected function tearDown() - { - parent::tearDown(); - - $this->entityManager->close(); - $this->entityManager = null; // avoid memory leaks - } - }