From 2823a40c4d9ab524ab3d29f9b0f0f13fd9a94919 Mon Sep 17 00:00:00 2001 From: Sebastian Goettschkes Date: Sat, 30 Mar 2013 14:45:13 +0100 Subject: [PATCH 1/2] Adding a database cookbook with the topics unit tests and config_test.yml covered --- cookbook/map.rst.inc | 1 + cookbook/testing/database.rst | 114 ++++++++++++++++++++++++++++++++++ cookbook/testing/index.rst | 1 + 3 files changed, 116 insertions(+) create mode 100644 cookbook/testing/database.rst diff --git a/cookbook/map.rst.inc b/cookbook/map.rst.inc index bf372309ff9..fef51cb189f 100644 --- a/cookbook/map.rst.inc +++ b/cookbook/map.rst.inc @@ -138,6 +138,7 @@ * :doc:`/cookbook/testing/http_authentication` * :doc:`/cookbook/testing/insulating_clients` * :doc:`/cookbook/testing/profiling` + * :doc:`/cookbook/testing/database` * :doc:`/cookbook/testing/doctrine` * :doc:`/cookbook/testing/bootstrap` * (email) :doc:`/cookbook/email/testing` diff --git a/cookbook/testing/database.rst b/cookbook/testing/database.rst new file mode 100644 index 00000000000..6909571b081 --- /dev/null +++ b/cookbook/testing/database.rst @@ -0,0 +1,114 @@ +.. index:: + single: Tests; Database + +How to test code which 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 +how 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, so a test always has the same data +to work with. + +Mocking the ``Repository`` in a Unit Test +----------------------------------------- + +If you want to test code which depends on a doctrine ``Repository`` in isolation, you +need to mock the ``Repository``. Normally you get the ``Repository`` from the ``EntityManager``, +so you would need to mock those as well. Suppose the class you want to test looks like this:: + + namespace Acme\DemoBundle\Salary; + + use Doctrine\Common\Persistence\ObjectManager; + + class SalaryCalculator + { + private $entityManager; + + public function __construct(ObjectManager $entityManager) + { + $this->entityManager = $entityManager; + } + + public function calculateTotalSalary($id) + { + $employeeRepository = $this->entityManager->getRepository('AcmeDemoBundle::Employee'); + $employee = $userRepository->find($id); + + return $employee->getSalary() + $employee->getBonus(); + } + } + +As the ``ObjectManager`` gets injected into the class through the constructor, it's +easy to pass a mock object within a test:: + + use Acme\DemoBundle\Salary\SalaryCalculator; + + class SalaryCalculatorTest extends \PHPUnit_Framework_TestCase + { + + public function testCalculateTotalSalary() + { + // First, mock the object to be used in the test + $employee = $this->getMock('\Acme\DemoBundle\Entity\Employee'); + $employee->expects($this->once()) + ->method('getSalary') + ->will($this->returnValue(1000)); + $employee->expects($this->once()) + ->method('getBonus') + ->will($this->returnValue(1100)); + + // Now, mock tthe repository so it returns the mock of the employee + $employeeRepository = $this->getMockBuilder('\Doctrine\ORM\EntityRepository') + ->disableOriginalConstructor() + ->getMock(); + $employeeRepository->expects($this->once()) + ->method('__call') + ->will($this->returnValue($employee)); + + // Last, mock the EntityManager to return the mock of the repository + $entityManager = $this->getMockBuilder('\Doctrine\ORM\EntityManager') + ->disableOriginalConstructor() + ->getMock(); + $entityManager->expects($this->once()) + ->method('getRepository') + ->will($this->returnValue($employeeRepository)); + + $salaryCalculator = new SalaryCalculator($entityManager); + $this->assertEquals(1100, $salaryCalculator->calculateTotalSalary(1)); + } + } + +We are building our mocks from the inside out, first creating the employee which +gets returned by the ``Repository`` which itself gets returned by the ``EntityManager``. +This way, no real class is involved in testing. + +.. note:: + + As a ``Repository`` doesn't have a ``find()`` method but uses the magic method + ``__call``, you cannot define a mock method on ``find()`` but must instead use + ``__call``. It's possible to implement the ``find()`` method in your ``Repository`` + and use it when mocking. In this case, make sure to mock your ``Repository`` + class instead of the generic ``EntityRepository``. + +Changing database settings for 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. + +To do this, you can specify the database configuration inside your ``app/config/config_test.yml``:: + + doctrine: + dbal: + host: localhost + dbname: testdb + user: testdb + password: testdb + +Make sure that your database runs on localhost and has the defined database and +user credentials set up. + diff --git a/cookbook/testing/index.rst b/cookbook/testing/index.rst index 0aab1fac566..a8b8f3b7c08 100644 --- a/cookbook/testing/index.rst +++ b/cookbook/testing/index.rst @@ -7,5 +7,6 @@ Testing http_authentication insulating_clients profiling + database doctrine bootstrap From 00d24fa324130a0a46cf26614727789f0e8cf54b Mon Sep 17 00:00:00 2001 From: Sebastian Goettschkes Date: Sat, 30 Mar 2013 20:58:41 +0100 Subject: [PATCH 2/2] Making some minor bugfixes to the testing/database cookbook. Added different code examples --- cookbook/testing/database.rst | 48 +++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/cookbook/testing/database.rst b/cookbook/testing/database.rst index 6909571b081..bff7631789b 100644 --- a/cookbook/testing/database.rst +++ b/cookbook/testing/database.rst @@ -59,16 +59,16 @@ easy to pass a mock object within a test:: ->method('getBonus') ->will($this->returnValue(1100)); - // Now, mock tthe repository so it returns the mock of the employee + // Now, mock the repository so it returns the mock of the employee $employeeRepository = $this->getMockBuilder('\Doctrine\ORM\EntityRepository') ->disableOriginalConstructor() ->getMock(); $employeeRepository->expects($this->once()) - ->method('__call') + ->method('find') ->will($this->returnValue($employee)); // Last, mock the EntityManager to return the mock of the repository - $entityManager = $this->getMockBuilder('\Doctrine\ORM\EntityManager') + $entityManager = $this->getMockBuilder('\Doctrine\Common\Persistence\ObjectManager') ->disableOriginalConstructor() ->getMock(); $entityManager->expects($this->once()) @@ -83,14 +83,6 @@ easy to pass a mock object within a test:: We are building our mocks from the inside out, first creating the employee which gets returned by the ``Repository`` which itself gets returned by the ``EntityManager``. This way, no real class is involved in testing. - -.. note:: - - As a ``Repository`` doesn't have a ``find()`` method but uses the magic method - ``__call``, you cannot define a mock method on ``find()`` but must instead use - ``__call``. It's possible to implement the ``find()`` method in your ``Repository`` - and use it when mocking. In this case, make sure to mock your ``Repository`` - class instead of the generic ``EntityRepository``. Changing database settings for functional tests ----------------------------------------------- @@ -100,15 +92,43 @@ 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. -To do this, you can specify the database configuration inside your ``app/config/config_test.yml``:: +To do this, you can specify a database configuration which overwrites the default +configuration: +.. code-block:: yaml + + # app/config/config_test.yml doctrine: + # ... dbal: host: localhost dbname: testdb user: testdb password: testdb -Make sure that your database runs on localhost and has the defined database and -user credentials set up. +.. code-block:: xml + + + + + +.. code-block:: php + + // app/config/config_test.php + $configuration->loadFromExtension('doctrine', array( + 'dbal' => array( + 'host' => 'localhost', + 'dbname' => 'testdb', + 'user' => 'testdb', + 'password' => 'testdb', + ), + )); + +Make sure that your database runs on localhost and has the defined database and +user credentials set up. \ No newline at end of file