4
4
How to Test Code that Interacts with the Database
5
5
=================================================
6
6
7
- If your code interacts with the database, e.g. reads data from or stores data
8
- into it, you need to adjust your tests to take this into account. There are
9
- many ways to deal with this. In a unit test, you can create a mock for
10
- a ``Repository `` and use it to return expected objects. In a functional test,
11
- you may need to prepare a test database with predefined values to ensure that
12
- your test always has the same data to work with.
7
+ Configuring a Database for Tests
8
+ --------------------------------
13
9
14
- .. note ::
10
+ Tests that interact with the database should use their own separate database to
11
+ not mess with the databases used in the other :ref: `configuration environments <configuration-environments >`.
12
+ To do that, edit or create the ``.env.test.local `` file at the root directory of
13
+ your project and define the new value for the ``DATABASE_URL `` env var:
15
14
16
- If you want to test your queries directly, see :doc: `/testing/doctrine `.
15
+ .. code-block :: bash
16
+
17
+ # .env.test.local
18
+ DATABASE_URL=mysql://USERNAME:PASSWORD@127.0.0.1/DB_NAME
17
19
18
20
.. tip ::
19
21
20
- A popular technique to improve the performance of tests that interact with
21
- the database is to begin a transaction before every test and roll it back
22
- after the test has finished. This makes it unnecessary to recreate the
23
- database or reload fixtures before every test. A community bundle called
24
- `DoctrineTestBundle `_ provides this feature.
22
+ A common practice is to append the ``_test `` suffix to the original database
23
+ names in tests. If the database name in production is called ``project_acme ``
24
+ the name of the testing database could be ``project_acme_test ``.
25
25
26
- Mocking the ``Repository `` in a Unit Test
27
- -----------------------------------------
26
+ The above assumes that each developer/machine uses a different database for the
27
+ tests. If the entire team uses the same settings for tests, edit or create the
28
+ ``.env.test `` file instead and commit it to the shared repository. Learn more
29
+ about :ref: `using multiple .env files in Symfony applications <configuration-multiple-env-files >`.
28
30
29
- If you want to test code which depends on a Doctrine repository in isolation,
30
- you need to mock the ``Repository ``. Normally you inject the ``EntityManager ``
31
- into your class and use it to get the repository. This makes things a little
32
- more difficult as you need to mock both the ``EntityManager `` and your repository
33
- class.
31
+ Resetting the Database Automatically Before each Test
32
+ -----------------------------------------------------
34
33
35
- .. tip ::
34
+ Tests should be independent from each other to avoid side effects. However, if
35
+ some test modifies the database (for example by adding or removing an element)
36
+ it could impact the other tests. Run the following command to install a bundle
37
+ that ensures that each test is run with a new and unmodified copy of the database:
38
+
39
+ .. code-block :: terminal
40
+
41
+ $ composer require --dev dama/doctrine-test-bundle
42
+
43
+ Now, enable it as a PHPUnit extension (PHPUnit 8 or higher) or a listener
44
+ (PHPUnit 7 or lower):
45
+
46
+ .. code-block :: xml
47
+
48
+ <!-- phpunit.xml.dist -->
49
+ <phpunit >
50
+ <!-- ... -->
51
+
52
+ <!-- Add this in PHPUnit 8 or higher -->
53
+ <extensions >
54
+ <extension class =" DAMA\DoctrineTestBundle\PHPUnit\PHPUnitExtension" />
55
+ </extensions >
56
+
57
+ <!-- Add this in PHPUnit 7 or lower -->
58
+ <listeners >
59
+ <listener class =" \DAMA\DoctrineTestBundle\PHPUnit\PHPUnitListener" />
60
+ </listeners >
61
+ </phpunit >
62
+
63
+ This bundle uses a clever trick to create a new database for each test without
64
+ scarifying performance: it begins a transaction before every test case and rolls
65
+ it back automatically after the test finished. Read more in the documentation of
66
+ the `DAMADoctrineTestBundle `_.
67
+
68
+ .. _doctrine-fixtures :
36
69
37
- It is possible (and a good idea) to inject your repository directly by
38
- registering your repository as a :doc: `factory service </service_container/factories >`.
39
- This is a little bit more work to setup, but makes testing easier as you
40
- only need to mock the repository.
70
+ Dummy Data Fixtures
71
+ -------------------
72
+
73
+ Instead of using the real data from the production database, it's common to use
74
+ fake or dummy data in the test database. This is usually called *"fixtures data" *
75
+ and Doctrine provides a library to create and load them. Install it with:
76
+
77
+ .. code-block :: terminal
78
+
79
+ $ composer require --dev doctrine/doctrine-fixtures-bundle
80
+
81
+ Then, use the ``make:fixtures `` command to generate an empty fixture class:
82
+
83
+ .. code-block :: terminal
84
+
85
+ $ php bin/console make:fixtures
86
+
87
+ The class name of the fixtures to create (e.g. AppFixtures):
88
+ > ProductFixture
89
+
90
+ Customize the new class to load ``Product `` objects into Doctrine::
91
+
92
+ // src/DataFixtures/ProductFixture.php
93
+ namespace App\DataFixtures;
94
+
95
+ use Doctrine\Bundle\FixturesBundle\Fixture;
96
+ use Doctrine\Common\Persistence\ObjectManager;
97
+
98
+ class ProductFixture extends Fixture
99
+ {
100
+ public function load(ObjectManager $manager)
101
+ {
102
+ $product = new Product();
103
+ $product->setName('Priceless widget!');
104
+ $product->setPrice(14.50);
105
+ $product->setDescription('Ok, I guess it *does* have a price');
106
+ $manager->persist($product);
107
+
108
+ // add more products
109
+
110
+ $manager->flush();
111
+ }
112
+ }
113
+
114
+ Empty the database and reload *all * the fixture classes with:
115
+
116
+ .. code-block :: terminal
117
+
118
+ $ php bin/console doctrine:fixtures:load
119
+
120
+ For more information, read the `DoctrineFixturesBundle documentation `_.
121
+
122
+ Mocking a Doctrine Repository in Unit Tests
123
+ -------------------------------------------
124
+
125
+ **Unit testing Doctrine repositories is not recommended **. Repositories are
126
+ meant to be tested against a real database connection. However, in case you
127
+ still need to do this, look at the following example.
41
128
42
129
Suppose the class you want to test looks like this::
43
130
@@ -66,8 +153,8 @@ Suppose the class you want to test looks like this::
66
153
}
67
154
}
68
155
69
- Since the ``EntityManagerInterface `` gets injected into the class through the constructor,
70
- you can pass a mock object within a test::
156
+ Since the ``EntityManagerInterface `` gets injected into the class through the
157
+ constructor, you can pass a mock object within a test::
71
158
72
159
// tests/Salary/SalaryCalculatorTest.php
73
160
namespace App\Tests\Salary;
@@ -95,6 +182,8 @@ you can pass a mock object within a test::
95
182
->willReturn($employee);
96
183
97
184
// Last, mock the EntityManager to return the mock of the repository
185
+ // (this is not needed if the class being tested injects the
186
+ // repository it uses instead of the entire object manager)
98
187
$objectManager = $this->createMock(ObjectManager::class);
99
188
// use getMock() on PHPUnit 5.3 or below
100
189
// $objectManager = $this->getMock(ObjectManager::class);
@@ -112,26 +201,55 @@ the employee which gets returned by the ``Repository``, which itself gets
112
201
returned by the ``EntityManager ``. This way, no real class is involved in
113
202
testing.
114
203
115
- Changing Database Settings for Functional Tests
116
- -----------------------------------------------
204
+ Mocking a Doctrine Repository in Functional Tests
205
+ -------------------------------------------------
117
206
118
- If you have functional tests, you want them to interact with a real database.
119
- Most of the time you want to use a dedicated database connection to make sure
120
- not to overwrite data you entered when developing the application and also
121
- to be able to clear the database before every test.
207
+ In :ref: `functional tests <functional-tests >` you'll make queries to the
208
+ database using the actual Doctrine repositories, instead of mocking them. To do
209
+ so, get the entity manager via the service container as follows::
122
210
123
- To do this, you can override the value of the `` DATABASE_URL `` env var in the
124
- `` phpunit.xml.dist `` to use a different database for your tests:
211
+ // tests/Repository/ProductRepositoryTest.php
212
+ namespace App\Tests\Repository;
125
213
126
- .. code-block :: xml
214
+ use App\Entity\Product;
215
+ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
127
216
128
- <?xml version =" 1.0" charset =" utf-8" ?>
129
- <phpunit >
130
- <php >
131
- <!-- the value is the Doctrine connection string in DSN format -->
132
- <env name =" DATABASE_URL" value =" mysql://USERNAME:PASSWORD@127.0.0.1/DB_NAME" />
133
- </php >
134
- <!-- ... -->
135
- </phpunit >
217
+ class ProductRepositoryTest extends KernelTestCase
218
+ {
219
+ /**
220
+ * @var \Doctrine\ORM\EntityManager
221
+ */
222
+ private $entityManager;
223
+
224
+ protected function setUp()
225
+ {
226
+ $kernel = self::bootKernel();
227
+
228
+ $this->entityManager = $kernel->getContainer()
229
+ ->get('doctrine')
230
+ ->getManager();
231
+ }
232
+
233
+ public function testSearchByCategoryName()
234
+ {
235
+ $products = $this->entityManager
236
+ ->getRepository(Product::class)
237
+ ->searchByCategoryName('foo')
238
+ ;
239
+
240
+ $this->assertCount(1, $products);
241
+ }
242
+
243
+ protected function tearDown()
244
+ {
245
+ parent::tearDown();
246
+
247
+ // doing this is recommended to avoid memory leaks
248
+ $this->entityManager->close();
249
+ $this->entityManager = null;
250
+ }
251
+ }
136
252
137
253
.. _`DoctrineTestBundle` : https://github.com/dmaicher/doctrine-test-bundle
254
+ .. _`DAMADoctrineTestBundle` : https://github.com/dmaicher/doctrine-test-bundle
255
+ .. _`DoctrineFixturesBundle documentation` : https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html
0 commit comments