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:[email protected] /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. For example,
35
+ if some test modifies the database (by adding or removing an entity) it could
36
+ change the results of other tests. Run the following command to install a bundle
37
+ that ensures that each test is run with the same unmodified database:
38
+
39
+ .. code-block :: terminal
40
+
41
+ $ composer require --dev dama/doctrine-test-bundle
42
+
43
+ Now, enable it as a PHPUnit extension or listener:
44
+
45
+ .. code-block :: xml
46
+
47
+ <!-- phpunit.xml.dist -->
48
+ <phpunit >
49
+ <!-- ... -->
50
+
51
+ <!-- Add this in PHPUnit 8 or higher -->
52
+ <extensions >
53
+ <extension class =" DAMA\DoctrineTestBundle\PHPUnit\PHPUnitExtension" />
54
+ </extensions >
55
+
56
+ <!-- Add this in PHPUnit 7 or lower -->
57
+ <listeners >
58
+ <listener class =" \DAMA\DoctrineTestBundle\PHPUnit\PHPUnitListener" />
59
+ </listeners >
60
+ </phpunit >
61
+
62
+ This bundle uses a clever trick to avoid side effects without scarifying
63
+ performance: it begins a database transaction before every test and rolls it
64
+ back automatically after the test finishes to undo all changes. Read more in the
65
+ documentation of the `DAMADoctrineTestBundle `_.
66
+
67
+ .. _doctrine-fixtures :
36
68
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.
69
+ Dummy Data Fixtures
70
+ -------------------
71
+
72
+ Instead of using the real data from the production database, it's common to use
73
+ fake or dummy data in the test database. This is usually called *"fixtures data" *
74
+ and Doctrine provides a library to create and load them. Install it with:
75
+
76
+ .. code-block :: terminal
77
+
78
+ $ composer require --dev doctrine/doctrine-fixtures-bundle
79
+
80
+ Then, use the ``make:fixtures `` command to generate an empty fixture class:
81
+
82
+ .. code-block :: terminal
83
+
84
+ $ php bin/console make:fixtures
85
+
86
+ The class name of the fixtures to create (e.g. AppFixtures):
87
+ > ProductFixture
88
+
89
+ Customize the new class to load ``Product `` objects into Doctrine::
90
+
91
+ // src/DataFixtures/ProductFixture.php
92
+ namespace App\DataFixtures;
93
+
94
+ use Doctrine\Bundle\FixturesBundle\Fixture;
95
+ use Doctrine\Common\Persistence\ObjectManager;
96
+
97
+ class ProductFixture extends Fixture
98
+ {
99
+ public function load(ObjectManager $manager)
100
+ {
101
+ $product = new Product();
102
+ $product->setName('Priceless widget');
103
+ $product->setPrice(14.50);
104
+ $product->setDescription('Ok, I guess it *does* have a price');
105
+ $manager->persist($product);
106
+
107
+ // add more products
108
+
109
+ $manager->flush();
110
+ }
111
+ }
112
+
113
+ Empty the database and reload *all * the fixture classes with:
114
+
115
+ .. code-block :: terminal
116
+
117
+ $ php bin/console doctrine:fixtures:load
118
+
119
+ For more information, read the `DoctrineFixturesBundle documentation `_.
120
+
121
+ Mocking a Doctrine Repository in Unit Tests
122
+ -------------------------------------------
123
+
124
+ **Unit testing Doctrine repositories is not recommended **. Repositories are
125
+ meant to be tested against a real database connection. However, in case you
126
+ still need to do this, look at the following example.
41
127
42
128
Suppose the class you want to test looks like this::
43
129
@@ -66,8 +152,8 @@ Suppose the class you want to test looks like this::
66
152
}
67
153
}
68
154
69
- Since the ``EntityManagerInterface `` gets injected into the class through the constructor,
70
- you can pass a mock object within a test::
155
+ Since the ``EntityManagerInterface `` gets injected into the class through the
156
+ constructor, you can pass a mock object within a test::
71
157
72
158
// tests/Salary/SalaryCalculatorTest.php
73
159
namespace App\Tests\Salary;
@@ -95,6 +181,8 @@ you can pass a mock object within a test::
95
181
->willReturn($employee);
96
182
97
183
// Last, mock the EntityManager to return the mock of the repository
184
+ // (this is not needed if the class being tested injects the
185
+ // repository it uses instead of the entire object manager)
98
186
$objectManager = $this->createMock(ObjectManager::class);
99
187
// use getMock() on PHPUnit 5.3 or below
100
188
// $objectManager = $this->getMock(ObjectManager::class);
@@ -112,26 +200,54 @@ the employee which gets returned by the ``Repository``, which itself gets
112
200
returned by the ``EntityManager ``. This way, no real class is involved in
113
201
testing.
114
202
115
- Changing Database Settings for Functional Tests
116
- -----------------------------------------------
203
+ Mocking a Doctrine Repository in Functional Tests
204
+ -------------------------------------------------
117
205
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.
206
+ In :ref: `functional tests <functional-tests >` you'll make queries to the
207
+ database using the actual Doctrine repositories, instead of mocking them. To do
208
+ so, get the entity manager via the service container as follows::
122
209
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:
210
+ // tests/Repository/ProductRepositoryTest.php
211
+ namespace App\Tests\Repository;
125
212
126
- .. code-block :: xml
213
+ use App\Entity\Product;
214
+ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
127
215
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:[email protected] /DB_NAME" />
133
- </php >
134
- <!-- ... -->
135
- </phpunit >
216
+ class ProductRepositoryTest extends KernelTestCase
217
+ {
218
+ /**
219
+ * @var \Doctrine\ORM\EntityManager
220
+ */
221
+ private $entityManager;
222
+
223
+ protected function setUp()
224
+ {
225
+ $kernel = self::bootKernel();
226
+
227
+ $this->entityManager = $kernel->getContainer()
228
+ ->get('doctrine')
229
+ ->getManager();
230
+ }
231
+
232
+ public function testSearchByName()
233
+ {
234
+ $product = $this->entityManager
235
+ ->getRepository(Product::class)
236
+ ->findOneBy(['name' => 'Priceless widget'])
237
+ ;
238
+
239
+ $this->assertSame(14.50, $product->getPrice());
240
+ }
241
+
242
+ protected function tearDown()
243
+ {
244
+ parent::tearDown();
245
+
246
+ // doing this is recommended to avoid memory leaks
247
+ $this->entityManager->close();
248
+ $this->entityManager = null;
249
+ }
250
+ }
136
251
137
- .. _`DoctrineTestBundle` : https://github.com/dmaicher/doctrine-test-bundle
252
+ .. _`DAMADoctrineTestBundle` : https://github.com/dmaicher/doctrine-test-bundle
253
+ .. _`DoctrineFixturesBundle documentation` : https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html
0 commit comments