{"id":2220,"date":"2013-08-14T12:04:28","date_gmt":"2013-08-14T12:04:28","guid":{"rendered":"https:\/\/intelligentbee.com\/blog\/?p=2220"},"modified":"2024-09-30T07:52:26","modified_gmt":"2024-09-30T07:52:26","slug":"symfony2-jobeet-day-8-the-unit-tests","status":"publish","type":"post","link":"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-8-the-unit-tests\/","title":{"rendered":"Symfony2 Jobeet Day 8: The Unit Tests"},"content":{"rendered":"<p><span style=\"font-family: timesnew roman; font-size: 12px;\">* This article is part of the original <a href=\"http:\/\/symfony.com\/legacy\/doc\/jobeet?orm=Doctrine\" target=\"_blank\" rel=\"noopener\">Jobeet Tutorial<\/a>, created by Fabien Potencier, for Symfony 1.4.<\/span><\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_68_1 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title \" >Table of Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-8-the-unit-tests\/#Unit_tests_in_jobeet_%E2%80%93_Tests_in_Symfony\" title=\"Unit tests in jobeet &#8211; Tests in Symfony\">Unit tests in jobeet &#8211; Tests in Symfony<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-8-the-unit-tests\/#Adding_Tests_for_new_Features\" title=\"Adding Tests for new Features\">Adding Tests for new Features<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-8-the-unit-tests\/#Adding_Tests_because_of_a_Bug\" title=\"Adding Tests because of a Bug\">Adding Tests because of a Bug<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-8-the-unit-tests\/#Towards_a_better_slugify_Method\" title=\"Towards a better slugify Method\">Towards a better slugify Method<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-8-the-unit-tests\/#Code_Coverage\" title=\"Code Coverage\">Code Coverage<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-8-the-unit-tests\/#Doctrine_Unit_Tests\" title=\"Doctrine Unit Tests\">Doctrine Unit Tests<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"Unit_tests_in_jobeet_%E2%80%93_Tests_in_Symfony\"><\/span>Unit tests in jobeet &#8211; Tests in Symfony<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>There are two different kinds of automated tests in Symfony: unit tests and functional tests. Unit tests verify that each method and function is working properly. Each test must be as independent as possible from the others. On the other hand, functional tests verify that the resulting application behaves correctly as a whole. <span id=\"more-186\"><\/span><\/p>\n<p>Unit tests will be covered in this post, whereas the next post will be dedicated to funcional tests.<\/p>\n<p>Symfony2 integrates with an independent library, the PHPUnit, to give you a rich testing framework. To run tests, you will have to install PHPUnit 3.5.11 or later.<\/p>\n<blockquote><p>If you don\u2019t have PHPUnit installed, use the following to get it:<\/p>\n<pre class=\"toolbar:2 lang:default decode:true \">sudo apt-get install phpunit\r\nsudo pear channel-discover pear.phpunit.de\r\nsudo pear channel-discover pear.symfony-project.com\r\nsudo pear channel-discover components.ez.no\r\nsudo pear channel-discover pear.symfony.com\r\nsudo pear update-channels\r\nsudo pear upgrade-all\r\nsudo pear install pear.symfony.com\/Yaml\r\nsudo pear install --alldeps phpunit\/PHPUnit\r\nsudo pear install --force --alldeps phpunit\/PHPUnit<\/pre>\n<\/blockquote>\n<p>Each test \u2013 whether it\u2019s a unit test or a functional test \u2013 is a PHP class that should live in the <code>Tests\/<\/code> subdirectory of your bundles. If you follow this rule, then you can run all of your application\u2019s tests with the following command:<\/p>\n<pre class=\"show-lang:2 nums:false lang:default decode:true \">phpunit -c app\/<\/pre>\n<p>The <code>-c<\/code> option tells PHPUnit to look in the <code>app\/<\/code> directory for a configuration file. If you\u2019re curious about the PHPUnit options, check out the <code>app\/phpunit.xml.dist<\/code> file.<\/p>\n<p>A unit test is usually a test against a specific PHP class. Let\u2019s start by writing tests for the <code>Jobeet:slugify()<\/code> method.<\/p>\n<p>Create a new file, <code>JobeetTest.php<\/code>, in the <code>src\/Ibw\/JobeetBundle\/Tests\/Utils<\/code> folder. By convention, the <code>Tests\/<\/code> subdirectory should replicate the directory of your bundle. So, when we are testing a class in our bundle\u2019s <code>Utils\/<\/code> directory, we put the test in the <code>Tests\/Utils\/ <\/code>directory:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Tests\/Utils\/JobeetTest.php\">namespace IbwJobeetBundleTestsUtils;\r\n\r\nuse IbwJobeetBundleUtilsJobeet;\r\n\r\nclass JobeetTest extends PHPUnit_Framework_TestCase\r\n{\r\n    public function testSlugify()\r\n    {\r\n        $this-&amp;gt;assertEquals('sensio', Jobeet::slugify('Sensio'));\r\n        $this-&amp;gt;assertEquals('sensio-labs', Jobeet::slugify('sensio labs'));\r\n        $this-&amp;gt;assertEquals('sensio-labs', Jobeet::slugify('sensio labs'));\r\n        $this-&amp;gt;assertEquals('paris-france', Jobeet::slugify('paris,france'));\r\n        $this-&amp;gt;assertEquals('sensio', Jobeet::slugify(' sensio'));\r\n        $this-&amp;gt;assertEquals('sensio', Jobeet::slugify('sensio '));\r\n    }\r\n}<\/pre>\n<p>To run only this test, you can use the following command:<\/p>\n<pre class=\"toolbar:2 nums:false lang:default decode:true \">phpunit -c app\/ src\/Ibw\/JobeetBundle\/Tests\/Utils\/JobeetTest<\/pre>\n<p>As everything should work fine, you should get the following result:<\/p>\n<pre class=\"toolbar:2 lang:default decode:true \">PHPUnit 3.7.22 by Sebastian Bergmann.\r\n\r\nConfiguration read from \/var\/www\/jobeet\/app\/phpunit.xml.dist\r\n\r\n.\r\n\r\nTime: 0 seconds, Memory: 8.00Mb\r\n\r\nOK (1 test, 6 assertions)<\/pre>\n<p>For a full list of <a href=\"http:\/\/www.phpunit.de\/manual\/current\/en\/writing-tests-for-phpunit.html#writing-tests-for-phpunit.assertions\" target=\"_blank\" rel=\"noopener\">assertions<\/a>, you can check the PHPUnit documentation.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Adding_Tests_for_new_Features\"><\/span>Adding Tests for new Features<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The slug for an empty string is an empty string. You can test it, it will work. But an empty string in a URL is not that a great idea. Let\u2019s change the <code>slugify()<\/code> method so that it returns the <code>\u201cn-a\u201d<\/code> string in case of an empty string.<\/p>\n<p>You can write the test first, then update the method, or the other way around. It is really a matter of taste, but writing the test first gives you the confidence that your code actually implements what you planned:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Tests\/Utils\/JobeetTest.php\">\/\/ ...\r\n\r\n$this-&amp;gt;assertEquals('n-a', Jobeet::slugify(''));\r\n\r\n\/\/ ...<\/pre>\n<p>Now, if we run the test again, we will have a failure:<\/p>\n<pre class=\"toolbar:2 lang:default decode:true \">PHPUnit 3.7.22 by Sebastian Bergmann.\r\n\r\nConfiguration read from \/var\/www\/jobeet\/app\/phpunit.xml.dist\r\n\r\nF\r\n\r\nTime: 0 seconds, Memory: 8.25Mb\r\n\r\nThere was 1 failure:\r\n\r\n1) IbwJobeetBundleTestsUtilsJobeetTest::testSlugify\r\nFailed asserting that two strings are equal.\r\n--- Expected\r\n+++ Actual\r\n@@ @@\r\n-'n-a'\r\n+''\r\n\r\n\/var\/www\/jobeet\/src\/Ibw\/JobeetBundle\/Tests\/Utils\/JobeetTest.php:13\r\n\r\nFAILURES!\r\nTests: 1, Assertions: 5, Failures: 1.<\/pre>\n<p>Now, edit the <code>Jobeet::slugify<\/code> method and add the following condition at the beginning:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Utils\/Jobeet.php\">\/\/ ...\r\n\r\n    static public function slugify($text)\r\n    {\r\n        if (empty($text)) {\r\n            return 'n-a';\r\n        }\r\n\r\n        \/\/ ...\r\n    }<\/pre>\n<p>The test must now pass as expected, and you can enjoy the green bar.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Adding_Tests_because_of_a_Bug\"><\/span>Adding Tests because of a Bug<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Let\u2019s say that time has passed and one of your users reports a weird bug: some job links point to a 404 error page. After some investigation, you find that for some reason, these jobs have an empty company, position, or location slug.<\/p>\n<p>How is it possible?<\/p>\n<p>You look through the records in the database and the columns are definitely not empty. You think about it for a while, and bingo, you find the cause. When a string only contains non-ASCII characters, the <code>slugify()<\/code> method converts it to an empty string. So happy to have found the cause, you open the <code>Jobeet<\/code> class and fix the problem right away. That\u2019s a bad idea. First, let\u2019s add a test:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Tests\/Utils\/JobeetTest.php\">$this-&gt;assertEquals('n-a', Jobeet::slugify(' - '));<\/pre>\n<p>After checking that the test does not pass, edit the <code>Jobeet<\/code> class and move the empty string check to the end of the method:<\/p>\n<pre class=\"lang:default decode:true \" title=\"src\/Ibw\/JobeetBundle\/Utils\/Jobeet.php\">static public function slugify($text)\r\n{\r\n    \/\/ ...\r\n\r\n    if (empty($text))\r\n    {\r\n        return 'n-a';\r\n    }\r\n\r\n    return $text;\r\n}<\/pre>\n<p>The new test now passes, as do all the other ones. The <code>slugify()<\/code> had a bug despite our 100% coverage.<\/p>\n<p>You cannot think about all edge cases when writing tests, and that\u2019s fine. But when you discover one, you need to write a test for it before fixing your code. It also means that your code will get better over time, which is always a good thing.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Towards_a_better_slugify_Method\"><\/span>Towards a better <code>slugify<\/code> Method<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>You probably know that symfony has been created by French people, so let\u2019s add a test with a French word that contains an \u201caccent\u201d:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Tests\/Utils\/JobeetTest.php\">$this-&gt;assertEquals('developpeur-web', Jobeet::slugify('D\u00e9veloppeur Web'));<\/pre>\n<p>The test must fail. Instead of replacing <code>\u00e9<\/code> by <code>e<\/code>, the <code>slugify()<\/code> method has replaced it by a dash (<code>-<\/code>). That\u2019s a tough problem, called <code>transliteration<\/code>. Hopefully, if you have <strong>iconv Library <\/strong>installed, it will do the job for us. Replace the code of the <code>slugify<\/code> method with the following:<\/p>\n<p>The test must fail. Instead of replacing <code>\u00e9<\/code> by <code>e<\/code>, the <code>slugify()<\/code> method has replaced it by a dash (<code>-<\/code>). That\u2019s a tough problem, called <code>transliteration<\/code>. Hopefully, if you have <strong>iconv Library <\/strong>installed, it will do the job for us. Replace the code of the <code>slugify<\/code> method with the following:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Utils\/Jobeet.php\">static public function slugify($text)\r\n{\r\n    \/\/ replace non letter or digits by -\r\n    $text = preg_replace('#[^\\pLd]+#u', '-', $text);\r\n\r\n    \/\/ trim\r\n    $text = trim($text, '-');\r\n\r\n    \/\/ transliterate\r\n    if (function_exists('iconv'))\r\n    {\r\n        $text = iconv('utf-8', 'us-ascii\/\/TRANSLIT', $text);\r\n    }\r\n\r\n    \/\/ lowercase\r\n    $text = strtolower($text);\r\n\r\n    \/\/ remove unwanted characters\r\n    $text = preg_replace('#[^-w]+#', '', $text);\r\n\r\n    if (empty($text))\r\n    {\r\n        return 'n-a';\r\n    }\r\n\r\n    return $text;\r\n}<\/pre>\n<p>Remember to save all your PHP files with the UTF-8 encoding, as this is the default Symfony encoding, and the one used by <code>iconv<\/code> to do the transliteration.<\/p>\n<p>Also change the test file to run the test only if <code>iconv<\/code> is available:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Tests\/Utils\/JobeetTest.php\">if (function_exists('iconv')) {\r\n    $this-&amp;gt;assertEquals('developpeur-web', Jobeet::slugify('D\u00e9veloppeur Web'));\r\n}<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"Code_Coverage\"><\/span>Code Coverage<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>When you write tests, it is easy to forget a portion of the code. If you add a new feature or you just want to verify your code coverage statistics, all you need to do is to check the code coverage by using the <code>--coverage-html<\/code> option:<\/p>\n<pre class=\"toolbar:2 nums:false lang:default decode:true \">phpunit --coverage-html=web\/cov\/ -c app\/<\/pre>\n<div>\n<div id=\"highlighter_170076\"><span style=\"line-height: 1.714285714; font-size: 1rem;\">Check the code coverage by opening the generated <\/span><code style=\"line-height: 1.714285714;\">http:\/\/jobeet.local\/cov\/index.html<\/code><span style=\"line-height: 1.714285714; font-size: 1rem;\"> page in a browser.<\/span><\/div>\n<blockquote>\n<div>\n<p>The code coverage only works if you have XDebug enabled and all dependencies installed.<\/p>\n<pre class=\"toolbar:2 nums:false lang:default decode:true \">sudo apt-get install php5-xdebug<\/pre>\n<\/div>\n<\/blockquote>\n<div>\n<p>Your <code>cov\/index.html<\/code> should look like this:<\/p>\n<p><a href=\"\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/03\/day-8-code-coverage1.jpg\"><img decoding=\"async\" class=\"alignnone wp-image-62 size-full\" src=\"\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/03\/day-8-code-coverage1.jpg\" alt=\"Unit tests in jobeet - Tests in Symfony\n\" width=\"1206\" height=\"492\" \/><\/a><\/p>\n<p>Keep in mind that when this indicates that your code is fully unit tested, it just means that each line has been executed, not that all the edge cases have been tested.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Doctrine_Unit_Tests\"><\/span>Doctrine Unit Tests<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Unit testing a Doctrine model class is a bit more complex as it requires a database connection. You already have the one you use for your development, but it is a good habit to create a dedicated database for tests.<\/p>\n<p>At the beginning of this tutorial, we introduced the environments as a way to vary an application\u2019s settings. By default, all symfony tests are run in the <code>test<\/code> environment, so let\u2019s configure a different database for the <code>test<\/code> environment:<\/p>\n<p>Go to your <code>app\/config<\/code> directory and create a copy of <code>parameters.yml<\/code> file, called <code>parameters_test.yml<\/code>. Open <code>parameters_test.yml<\/code> and change the name of your database to <code>jobeet_test<\/code>. For this to be imported, we have to add it in the <code>config_test.yml<\/code> file :<\/p>\n<pre class=\"lang:yaml decode:true \" title=\"app\/config\/config_test.yml\">imports:\r\n    - { resource: config_dev.yml }\r\n    - { resource: parameters_test.yml }\r\n\/\/ ...<\/pre>\n<h3>Testing the <code>Job<\/code> Entity<\/h3>\n<p>First, we need to create the <code>JobTest.php<\/code> file in the <code>Tests\/Entity<\/code> folder.<\/p>\n<p>The <code>setUp<\/code> function will manipulate your database each time you will run the test. At first, it will drop your current database, then it will re-create it and load data from fixtures in it. This will help you have the same initial data in the database you created for the test environment before running the tests.<\/p>\n<pre class=\"lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Tests\/Entity\/JobTest.php\">namespace IbwJobeetBundleEntity;\r\n\r\nuse SymfonyBundleFrameworkBundleTestWebTestCase;\r\nuse IbwJobeetBundleUtilsJobeet as Jobeet;\r\nuse SymfonyBundleFrameworkBundleConsoleApplication;\r\nuse SymfonyComponentConsoleOutputNullOutput;\r\nuse SymfonyComponentConsoleInputArrayInput;\r\nuse DoctrineBundleDoctrineBundleCommandDropDatabaseDoctrineCommand;\r\nuse DoctrineBundleDoctrineBundleCommandCreateDatabaseDoctrineCommand;\r\nuse DoctrineBundleDoctrineBundleCommandProxyCreateSchemaDoctrineCommand;\r\n\r\nclass JobTest extends WebTestCase\r\n{\r\n    private $em;\r\n    private $application;\r\n\r\n    public function setUp()\r\n    {\r\n        static::$kernel = static::createKernel();\r\n        static::$kernel-&gt;boot();\r\n\r\n        $this-&gt;application = new Application(static::$kernel);\r\n\r\n        \/\/ drop the database\r\n        $command = new DropDatabaseDoctrineCommand();\r\n        $this-&gt;application-&gt;add($command);\r\n        $input = new ArrayInput(array(\r\n            'command' =&gt; 'doctrine:database:drop',\r\n            '--force' =&gt; true\r\n        ));\r\n        $command-&gt;run($input, new NullOutput());\r\n\r\n        \/\/ we have to close the connection after dropping the database so we don't get \"No database selected\" error\r\n        $connection = $this-&gt;application-&gt;getKernel()-&gt;getContainer()-&gt;get('doctrine')-&gt;getConnection();\r\n        if ($connection-&gt;isConnected()) {\r\n            $connection-&gt;close();\r\n        }\r\n\r\n        \/\/ create the database\r\n        $command = new CreateDatabaseDoctrineCommand();\r\n        $this-&gt;application-&gt;add($command);\r\n        $input = new ArrayInput(array(\r\n            'command' =&gt; 'doctrine:database:create',\r\n        ));\r\n        $command-&gt;run($input, new NullOutput());\r\n\r\n        \/\/ create schema\r\n        $command = new CreateSchemaDoctrineCommand();\r\n        $this-&gt;application-&gt;add($command);\r\n        $input = new ArrayInput(array(\r\n            'command' =&gt; 'doctrine:schema:create',\r\n        ));\r\n        $command-&gt;run($input, new NullOutput());\r\n\r\n        \/\/ get the Entity Manager\r\n        $this-&gt;em = static::$kernel-&gt;getContainer()\r\n            -&gt;get('doctrine')\r\n            -&gt;getManager();\r\n\r\n        \/\/ load fixtures\r\n        $client = static::createClient();\r\n        $loader = new SymfonyBridgeDoctrineDataFixturesContainerAwareLoader($client-&gt;getContainer());\r\n        $loader-&gt;loadFromDirectory(static::$kernel-&gt;locateResource('@IbwJobeetBundle\/DataFixtures\/ORM'));\r\n        $purger = new DoctrineCommonDataFixturesPurgerORMPurger($this-&gt;em);\r\n        $executor = new DoctrineCommonDataFixturesExecutorORMExecutor($this-&gt;em, $purger);\r\n        $executor-&gt;execute($loader-&gt;getFixtures());\r\n    }\r\n\r\n    public function testGetCompanySlug()\r\n    {\r\n        $job = $this-&gt;em-&gt;createQuery('SELECT j FROM IbwJobeetBundle:Job j ')\r\n            -&gt;setMaxResults(1)\r\n            -&gt;getSingleResult();\r\n\r\n        $this-&gt;assertEquals($job-&gt;getCompanySlug(), Jobeet::slugify($job-&gt;getCompany()));\r\n    }\r\n\r\n    public function testGetPositionSlug()\r\n    {\r\n        $job = $this-&gt;em-&gt;createQuery('SELECT j FROM IbwJobeetBundle:Job j ')\r\n            -&gt;setMaxResults(1)\r\n            -&gt;getSingleResult();\r\n\r\n        $this-&gt;assertEquals($job-&gt;getPositionSlug(), Jobeet::slugify($job-&gt;getPosition()));\r\n    }\r\n\r\n    public function testGetLocationSlug()\r\n    {\r\n        $job = $this-&gt;em-&gt;createQuery('SELECT j FROM IbwJobeetBundle:Job j ')\r\n            -&gt;setMaxResults(1)\r\n            -&gt;getSingleResult();\r\n\r\n        $this-&gt;assertEquals($job-&gt;getLocationSlug(), Jobeet::slugify($job-&gt;getLocation()));\r\n    }\r\n\r\n    public function testSetExpiresAtValue()\r\n    {\r\n        $job = new Job();\r\n        $job-&gt;setExpiresAtValue();\r\n\r\n        $this-&gt;assertEquals(time() + 86400 * 30, $job-&gt;getExpiresAt()-&gt;format('U'));\r\n    }\r\n\r\n    protected function tearDown()\r\n    {\r\n        parent::tearDown();\r\n        $this-&gt;em-&gt;close();\r\n    }\r\n}<\/pre>\n<h3>Testing the Repository Classes<\/h3>\n<p>Now, let\u2019s write some tests for the <code>JobRepository<\/code> class, to see if the functions we created in the previous days are returning the right values:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Tests\/Repository\/JobRepositoryTest.php\">namespace IbwJobeetBundleTestsRepository;\r\n\r\nuse SymfonyBundleFrameworkBundleTestWebTestCase;\r\nuse SymfonyBundleFrameworkBundleConsoleApplication;\r\nuse SymfonyComponentConsoleOutputNullOutput;\r\nuse SymfonyComponentConsoleInputArrayInput;\r\nuse DoctrineBundleDoctrineBundleCommandDropDatabaseDoctrineCommand;\r\nuse DoctrineBundleDoctrineBundleCommandCreateDatabaseDoctrineCommand;\r\nuse DoctrineBundleDoctrineBundleCommandProxyCreateSchemaDoctrineCommand;\r\n\r\nclass JobRepositoryTest extends WebTestCase\r\n{\r\n    private $em;\r\n    private $application;\r\n\r\n    public function setUp()\r\n    {\r\n        static::$kernel = static::createKernel();\r\n        static::$kernel-&gt;boot();\r\n\r\n        $this-&gt;application = new Application(static::$kernel);\r\n\r\n        \/\/ drop the database\r\n        $command = new DropDatabaseDoctrineCommand();\r\n        $this-&gt;application-&gt;add($command);\r\n        $input = new ArrayInput(array(\r\n            'command' =&gt; 'doctrine:database:drop',\r\n            '--force' =&gt; true\r\n        ));\r\n        $command-&gt;run($input, new NullOutput());\r\n\r\n        \/\/ we have to close the connection after dropping the database so we don't get \"No database selected\" error\r\n        $connection = $this-&gt;application-&gt;getKernel()-&gt;getContainer()-&gt;get('doctrine')-&gt;getConnection();\r\n        if ($connection-&gt;isConnected()) {\r\n            $connection-&gt;close();\r\n        }\r\n\r\n        \/\/ create the database\r\n        $command = new CreateDatabaseDoctrineCommand();\r\n        $this-&gt;application-&gt;add($command);\r\n        $input = new ArrayInput(array(\r\n            'command' =&gt; 'doctrine:database:create',\r\n        ));\r\n        $command-&gt;run($input, new NullOutput());\r\n\r\n        \/\/ create schema\r\n        $command = new CreateSchemaDoctrineCommand();\r\n        $this-&gt;application-&gt;add($command);\r\n        $input = new ArrayInput(array(\r\n            'command' =&gt; 'doctrine:schema:create',\r\n        ));\r\n        $command-&gt;run($input, new NullOutput());\r\n\r\n        \/\/ get the Entity Manager\r\n        $this-&gt;em = static::$kernel-&gt;getContainer()\r\n            -&gt;get('doctrine')\r\n            -&gt;getManager();\r\n\r\n        \/\/ load fixtures\r\n        $client = static::createClient();\r\n        $loader = new SymfonyBridgeDoctrineDataFixturesContainerAwareLoader($client-&gt;getContainer());\r\n        $loader-&gt;loadFromDirectory(static::$kernel-&gt;locateResource('@IbwJobeetBundle\/DataFixtures\/ORM'));\r\n        $purger = new DoctrineCommonDataFixturesPurgerORMPurger($this-&gt;em);\r\n        $executor = new DoctrineCommonDataFixturesExecutorORMExecutor($this-&gt;em, $purger);\r\n        $executor-&gt;execute($loader-&gt;getFixtures());\r\n    }\r\n\r\n    public function testCountActiveJobs()\r\n    {\r\n        $query = $this-&gt;em-&gt;createQuery('SELECT c FROM IbwJobeetBundle:Category c');\r\n        $categories = $query-&gt;getResult();\r\n\r\n        foreach($categories as $category) {\r\n            $query = $this-&gt;em-&gt;createQuery('SELECT COUNT(j.id) FROM IbwJobeetBundle:Job j WHERE j.category = :category AND j.expires_at &gt; :date');\r\n            $query-&gt;setParameter('category', $category-&gt;getId());\r\n            $query-&gt;setParameter('date', date('Y-m-d H:i:s', time()));\r\n            $jobs_db = $query-&gt;getSingleScalarResult();\r\n\r\n            $jobs_rep = $this-&gt;em-&gt;getRepository('IbwJobeetBundle:Job')-&gt;countActiveJobs($category-&gt;getId());\r\n            \/\/ This test will verify if the value returned by the countActiveJobs() function\r\n            \/\/ coincides with the number of active jobs for a given category from the database\r\n            $this-&gt;assertEquals($jobs_rep, $jobs_db);\r\n        }\r\n    }\r\n\r\n    public function testGetActiveJobs()\r\n    {\r\n        $query = $this-&gt;em-&gt;createQuery('SELECT c from IbwJobeetBundle:Category c');\r\n        $categories = $query-&gt;getResult();\r\n\r\n        foreach ($categories as $category) {\r\n            $query = $this-&gt;em-&gt;createQuery('SELECT COUNT(j.id) from IbwJobeetBundle:Job j WHERE j.expires_at &gt; :date AND j.category = :category');\r\n            $query-&gt;setParameter('date', date('Y-m-d H:i:s', time()));\r\n            $query-&gt;setParameter('category', $category-&gt;getId());\r\n            $jobs_db = $query-&gt;getSingleScalarResult();\r\n\r\n            $jobs_rep = $this-&gt;em-&gt;getRepository('IbwJobeetBundle:Job')-&gt;getActiveJobs($category-&gt;getId(), null, null);\r\n            \/\/ This test tells if the number of active jobs for a given category from\r\n            \/\/ the database is the same as the value returned by the function\r\n            $this-&gt;assertEquals($jobs_db, count($jobs_rep));\r\n        }\r\n    }\r\n\r\n    public function testGetActiveJob()\r\n    {\r\n        $query = $this-&gt;em-&gt;createQuery('SELECT j FROM IbwJobeetBundle:Job j WHERE j.expires_at &gt; :date');\r\n        $query-&gt;setParameter('date', date('Y-m-d H:i:s', time()));\r\n        $query-&gt;setMaxResults(1);\r\n        $job_db = $query-&gt;getSingleResult();\r\n\r\n        $job_rep = $this-&gt;em-&gt;getRepository('IbwJobeetBundle:Job')-&gt;getActiveJob($job_db-&gt;getId());\r\n        \/\/ If the job is active, the getActiveJob() method should return a non-null value\r\n        $this-&gt;assertNotNull($job_rep);\r\n\r\n        $query = $this-&gt;em-&gt;createQuery('SELECT j FROM IbwJobeetBundle:Job j WHERE j.expires_at &lt; :date');         $query-&gt;setParameter('date', date('Y-m-d H:i:s', time()));\r\n        $query-&gt;setMaxResults(1);\r\n        $job_expired = $query-&gt;getSingleResult();\r\n\r\n        $job_rep = $this-&gt;em-&gt;getRepository('IbwJobeetBundle:Job')-&gt;getActiveJob($job_expired-&gt;getId());\r\n        \/\/ If the job is expired, the getActiveJob() method should return a null value\r\n        $this-&gt;assertNull($job_rep);\r\n    }\r\n\r\n    protected function tearDown()\r\n    {\r\n        parent::tearDown();\r\n        $this-&gt;em-&gt;close();\r\n    }\r\n}<\/pre>\n<p>We will do the same thing for <code>CategoryRepository<\/code> class:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Tests\/Repository\/CategoryRepositoryTest.php\">namespace IbwJobeetBundleTestsRepository;\r\n\r\nuse SymfonyBundleFrameworkBundleTestWebTestCase;\r\nuse SymfonyBundleFrameworkBundleConsoleApplication;\r\nuse SymfonyComponentConsoleOutputNullOutput;\r\nuse SymfonyComponentConsoleInputArrayInput;\r\nuse DoctrineBundleDoctrineBundleCommandDropDatabaseDoctrineCommand;\r\nuse DoctrineBundleDoctrineBundleCommandCreateDatabaseDoctrineCommand;\r\nuse DoctrineBundleDoctrineBundleCommandProxyCreateSchemaDoctrineCommand;\r\n\r\nclass CategoryRepositoryTest extends WebTestCase\r\n{\r\n    private $em;\r\n    private $application;\r\n\r\n    public function setUp()\r\n    {\r\n        static::$kernel = static::createKernel();\r\n        static::$kernel-&gt;boot();\r\n\r\n        $this-&gt;application = new Application(static::$kernel);\r\n\r\n        \/\/ drop the database\r\n        $command = new DropDatabaseDoctrineCommand();\r\n        $this-&gt;application-&gt;add($command);\r\n        $input = new ArrayInput(array(\r\n            'command' =&gt; 'doctrine:database:drop',\r\n            '--force' =&gt; true\r\n        ));\r\n        $command-&gt;run($input, new NullOutput());\r\n\r\n        \/\/ we have to close the connection after dropping the database so we don't get \"No database selected\" error\r\n        $connection = $this-&gt;application-&gt;getKernel()-&gt;getContainer()-&gt;get('doctrine')-&gt;getConnection();\r\n        if ($connection-&gt;isConnected()) {\r\n            $connection-&gt;close();\r\n        }\r\n\r\n        \/\/ create the database\r\n        $command = new CreateDatabaseDoctrineCommand();\r\n        $this-&gt;application-&gt;add($command);\r\n        $input = new ArrayInput(array(\r\n            'command' =&gt; 'doctrine:database:create',\r\n        ));\r\n        $command-&gt;run($input, new NullOutput());\r\n\r\n        \/\/ create schema\r\n        $command = new CreateSchemaDoctrineCommand();\r\n        $this-&gt;application-&gt;add($command);\r\n        $input = new ArrayInput(array(\r\n            'command' =&gt; 'doctrine:schema:create',\r\n        ));\r\n        $command-&gt;run($input, new NullOutput());\r\n\r\n        \/\/ get the Entity Manager\r\n        $this-&gt;em = static::$kernel-&gt;getContainer()\r\n            -&gt;get('doctrine')\r\n            -&gt;getManager();\r\n\r\n        \/\/ load fixtures\r\n        $client = static::createClient();\r\n        $loader = new SymfonyBridgeDoctrineDataFixturesContainerAwareLoader($client-&gt;getContainer());\r\n        $loader-&gt;loadFromDirectory(static::$kernel-&gt;locateResource('@IbwJobeetBundle\/DataFixtures\/ORM'));\r\n        $purger = new DoctrineCommonDataFixturesPurgerORMPurger($this-&gt;em);\r\n        $executor = new DoctrineCommonDataFixturesExecutorORMExecutor($this-&gt;em, $purger);\r\n        $executor-&gt;execute($loader-&gt;getFixtures());\r\n    }\r\n\r\n    public function testGetWithJobs()\r\n    {\r\n        $query = $this-&gt;em-&gt;createQuery('SELECT c FROM IbwJobeetBundle:Category c LEFT JOIN c.jobs j WHERE j.expires_at &gt; :date');\r\n        $query-&gt;setParameter('date', date('Y-m-d H:i:s', time()));\r\n        $categories_db = $query-&gt;getResult();\r\n\r\n        $categories_rep = $this-&gt;em-&gt;getRepository('IbwJobeetBundle:Category')-&gt;getWithJobs();\r\n        \/\/ This test verifies if the number of categories having active jobs, returned\r\n        \/\/ by the getWithJobs() function equals the number of categories having active jobs from database\r\n        $this-&gt;assertEquals(count($categories_rep), count($categories_db));\r\n    }\r\n\r\n    protected function tearDown()\r\n    {\r\n        parent::tearDown();\r\n        $this-&gt;em-&gt;close();\r\n    }\r\n}<\/pre>\n<p>After you finish writing the tests, run them with the following command, in order to generate the code coverage percent for the whole functions :<\/p>\n<pre class=\"toolbar:2 nums:false lang:default decode:true \">phpunit --coverage-html=web\/cov\/ -c app src\/Ibw\/JobeetBundle\/Tests\/Repository\/<\/pre>\n<p>Now, if you go to http:\/\/jobeet.local\/cov\/Repository.html you will see that the code coverage for <code>Repository Tests<\/code> is not 100% complete.<\/p>\n<p><a href=\"\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/03\/Day-8-coverage-not-complete.jpg\"><img decoding=\"async\" class=\"alignnone wp-image-65 size-full\" src=\"\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/03\/Day-8-coverage-not-complete.jpg\" alt=\"Unit tests in jobeet \" width=\"1625\" height=\"477\" \/><\/a><\/p>\n<p>Let\u2019s add some tests for the <code>JobRepository<\/code> to achieve 100% code coverage. At the moment, in our database, we have two job categories having 0 active jobs and one job category having just one active job. That why, when we will test the <code>$max<\/code> and <code>$offset<\/code> parameters, we will run the following tests just on the categories with at least 3 active jobs. In order to do that, add this inside your <code>foreach<\/code> statement, from your <code>testGetActiveJobs()<\/code> function:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Tests\/Repository\/JobRepositoryTest.php\">\/\/ ...\r\nforeach ($categories as $category) {\r\n    \/\/ ...\r\n\r\n    \/\/ If there are at least 3 active jobs in the selected category, we will\r\n    \/\/ test the getActiveJobs() method using the limit and offset parameters too\r\n    \/\/ to get 100% code coverage\r\n    if($jobs_db &gt; 2 ) {\r\n        $jobs_rep = $this-&gt;em-&gt;getRepository('IbwJobeetBundle:Job')-&gt;getActiveJobs($category-&gt;getId(), 2);\r\n        \/\/ This test tells if the number of returned active jobs is the one $max parameter requires\r\n        $this-&gt;assertEquals(2, count($jobs_rep));\r\n\r\n        $jobs_rep = $this-&gt;em-&gt;getRepository('IbwJobeetBundle:Job')-&gt;getActiveJobs($category-&gt;getId(), 2, 1);\r\n        \/\/ We set the limit to 2 results, starting from the second job and test if the result is as expected\r\n        $this-&gt;assertEquals(2, count($jobs_rep));\r\n    }\r\n}\r\n\/\/ ...<\/pre>\n<p>Run the <code>code coverage<\/code> command again :<\/p>\n<pre class=\"toolbar:2 nums:false lang:default decode:true \">phpunit --coverage-html=web\/cov\/ -c app src\/Ibw\/JobeetBundle\/Tests\/Repository\/<\/pre>\n<p>This time, if you check your code coverage, you will see that it 100% complete.<\/p>\n<p><a href=\"\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/03\/Day-8-coverage-complete.jpg\"><img decoding=\"async\" class=\"alignnone wp-image-66 size-full\" src=\"\/\/intelligentbee.com\/blog\/wp-content\/uploads\/2017\/03\/Day-8-coverage-complete.jpg\" alt=\"Unit tests in jobeet - Tests Symfony\" width=\"1668\" height=\"483\" \/><\/a><\/p>\n<h4>That\u2019s all for today! See you tomorrow, when we will talk about functional tests.<\/h4>\n<\/div>\n<\/div>\n<p><a href=\"http:\/\/creativecommons.org\/licenses\/by-sa\/3.0\/\" rel=\"license\"><img decoding=\"async\" style=\"border-width: 0;\" src=\"\/\/i.creativecommons.org\/l\/by-sa\/3.0\/88x31.png\" alt=\"Creative Commons License\" \/><\/a><br \/>\nThis work is licensed under a <a href=\"http:\/\/creativecommons.org\/licenses\/by-sa\/3.0\/\" target=\"_blank\" rel=\"license noopener\">Creative Commons Attribution-ShareAlike 3.0 Unported License<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>* This article is part of the original Jobeet Tutorial, created by Fabien Potencier, for Symfony 1.4. Unit tests in [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[82],"tags":[],"yst_prominent_words":[394,528,970,1395],"post_mailing_queue_ids":[],"_links":{"self":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2220"}],"collection":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/comments?post=2220"}],"version-history":[{"count":3,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2220\/revisions"}],"predecessor-version":[{"id":133240,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2220\/revisions\/133240"}],"wp:attachment":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/media?parent=2220"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/categories?post=2220"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/tags?post=2220"},{"taxonomy":"yst_prominent_words","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/yst_prominent_words?post=2220"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}