Skip to content

Commit 53174e0

Browse files
committed
Merge pull request symfony#2407 from Sgoettschkes/issue480
[WIP] Adding a testing/database cookbook
2 parents 7627f76 + 00d24fa commit 53174e0

File tree

3 files changed

+136
-0
lines changed

3 files changed

+136
-0
lines changed

cookbook/map.rst.inc

+1
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@
143143
* :doc:`/cookbook/testing/simulating_authentication`
144144
* :doc:`/cookbook/testing/insulating_clients`
145145
* :doc:`/cookbook/testing/profiling`
146+
* :doc:`/cookbook/testing/database`
146147
* :doc:`/cookbook/testing/doctrine`
147148
* :doc:`/cookbook/testing/bootstrap`
148149
* (email) :doc:`/cookbook/email/testing`

cookbook/testing/database.rst

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
.. index::
2+
single: Tests; Database
3+
4+
How to test code which interacts with the database
5+
==================================================
6+
7+
If your code interacts with the database, e.g. reads data from or stores data into
8+
it, you need to adjust your tests to take this into account. There are many ways
9+
how to deal with this. In a unit test, you can create a mock for a ``Repository``
10+
and use it to return expected objects. In a functional test, you may need to
11+
prepare a test database with predefined values, so a test always has the same data
12+
to work with.
13+
14+
Mocking the ``Repository`` in a Unit Test
15+
-----------------------------------------
16+
17+
If you want to test code which depends on a doctrine ``Repository`` in isolation, you
18+
need to mock the ``Repository``. Normally you get the ``Repository`` from the ``EntityManager``,
19+
so you would need to mock those as well. Suppose the class you want to test looks like this::
20+
21+
namespace Acme\DemoBundle\Salary;
22+
23+
use Doctrine\Common\Persistence\ObjectManager;
24+
25+
class SalaryCalculator
26+
{
27+
private $entityManager;
28+
29+
public function __construct(ObjectManager $entityManager)
30+
{
31+
$this->entityManager = $entityManager;
32+
}
33+
34+
public function calculateTotalSalary($id)
35+
{
36+
$employeeRepository = $this->entityManager->getRepository('AcmeDemoBundle::Employee');
37+
$employee = $userRepository->find($id);
38+
39+
return $employee->getSalary() + $employee->getBonus();
40+
}
41+
}
42+
43+
As the ``ObjectManager`` gets injected into the class through the constructor, it's
44+
easy to pass a mock object within a test::
45+
46+
use Acme\DemoBundle\Salary\SalaryCalculator;
47+
48+
class SalaryCalculatorTest extends \PHPUnit_Framework_TestCase
49+
{
50+
51+
public function testCalculateTotalSalary()
52+
{
53+
// First, mock the object to be used in the test
54+
$employee = $this->getMock('\Acme\DemoBundle\Entity\Employee');
55+
$employee->expects($this->once())
56+
->method('getSalary')
57+
->will($this->returnValue(1000));
58+
$employee->expects($this->once())
59+
->method('getBonus')
60+
->will($this->returnValue(1100));
61+
62+
// Now, mock the repository so it returns the mock of the employee
63+
$employeeRepository = $this->getMockBuilder('\Doctrine\ORM\EntityRepository')
64+
->disableOriginalConstructor()
65+
->getMock();
66+
$employeeRepository->expects($this->once())
67+
->method('find')
68+
->will($this->returnValue($employee));
69+
70+
// Last, mock the EntityManager to return the mock of the repository
71+
$entityManager = $this->getMockBuilder('\Doctrine\Common\Persistence\ObjectManager')
72+
->disableOriginalConstructor()
73+
->getMock();
74+
$entityManager->expects($this->once())
75+
->method('getRepository')
76+
->will($this->returnValue($employeeRepository));
77+
78+
$salaryCalculator = new SalaryCalculator($entityManager);
79+
$this->assertEquals(1100, $salaryCalculator->calculateTotalSalary(1));
80+
}
81+
}
82+
83+
We are building our mocks from the inside out, first creating the employee which
84+
gets returned by the ``Repository`` which itself gets returned by the ``EntityManager``.
85+
This way, no real class is involved in testing.
86+
87+
Changing database settings for functional tests
88+
-----------------------------------------------
89+
90+
If you have functional tests, you want them to interact with a real database.
91+
Most of the time you want to use a dedicated database connection to make sure
92+
not to overwrite data you entered when developing the application and also
93+
to be able to clear the database before every test.
94+
95+
To do this, you can specify a database configuration which overwrites the default
96+
configuration:
97+
98+
.. code-block:: yaml
99+
100+
# app/config/config_test.yml
101+
doctrine:
102+
# ...
103+
dbal:
104+
host: localhost
105+
dbname: testdb
106+
user: testdb
107+
password: testdb
108+
109+
.. code-block:: xml
110+
111+
<!-- app/config/config_test.xml -->
112+
<doctrine:config>
113+
<doctrine:dbal
114+
host="localhost"
115+
dbname="testdb"
116+
user="testdb"
117+
password="testdb"
118+
>
119+
</doctrine:config>
120+
121+
.. code-block:: php
122+
123+
// app/config/config_test.php
124+
$configuration->loadFromExtension('doctrine', array(
125+
'dbal' => array(
126+
'host' => 'localhost',
127+
'dbname' => 'testdb',
128+
'user' => 'testdb',
129+
'password' => 'testdb',
130+
),
131+
));
132+
133+
Make sure that your database runs on localhost and has the defined database and
134+
user credentials set up.

cookbook/testing/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ Testing
88
simulating_authentication
99
insulating_clients
1010
profiling
11+
database
1112
doctrine
1213
bootstrap

0 commit comments

Comments
 (0)