* This article is part of the original Jobeet Tutorial, created by Fabien Potencier, for Symfony 1.4.

Tests in Symfony

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.

Unit tests will be covered in this post, whereas the next post will be dedicated to funcional tests.

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.

If you don’t have PHPUnit installed, use the following to get it:

Each test – whether it’s a unit test or a functional test – is a PHP class that should live in the Tests/ subdirectory of your bundles. If you follow this rule, then you can run all of your application’s tests with the following command:

The -c option tells PHPUnit to look in the app/ directory for a configuration file. If you’re curious about the PHPUnit options, check out the app/phpunit.xml.dist file.

A unit test is usually a test against a specific PHP class. Let’s start by writing tests for the Jobeet:slugify() method.

Create a new file, JobeetTest.php, in the src/Ibw/JobeetBundle/Tests/Utils folder. By convention, the Tests/ subdirectory should replicate the directory of your bundle. So, when we are testing a class in our bundle’s Utils/ directory, we put the test in the Tests/Utils/ directory:

To run only this test, you can use the following command:

As everything should work fine, you should get the following result:

For a full list of assertions, you can check the PHPUnit documentation.

Adding Tests for new Features

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’s change the slugify() method so that it returns the “n-a” string in case of an empty string.

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:

Now, if we run the test again, we will have a failure:

Now, edit the Jobeet::slugify method and add the following condition at the beginning:

The test must now pass as expected, and you can enjoy the green bar.

Adding Tests because of a Bug

Let’s 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.

How is it possible?

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 slugify() method converts it to an empty string. So happy to have found the cause, you open the Jobeet class and fix the problem right away. That’s a bad idea. First, let’s add a test:

After checking that the test does not pass, edit the Jobeet class and move the empty string check to the end of the method:

The new test now passes, as do all the other ones. The slugify() had a bug despite our 100% coverage.

You cannot think about all edge cases when writing tests, and that’s 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.

Towards a better slugify Method

You probably know that symfony has been created by French people, so let’s add a test with a French word that contains an “accent”:

The test must fail. Instead of replacing é by e, the slugify() method has replaced it by a dash (-). That’s a tough problem, called transliteration. Hopefully, if you have iconv Library installed, it will do the job for us. Replace the code of the slugify method with the following:

The test must fail. Instead of replacing é by e, the slugify() method has replaced it by a dash (-). That’s a tough problem, called transliteration. Hopefully, if you have iconv Library installed, it will do the job for us. Replace the code of the slugify method with the following:

Remember to save all your PHP files with the UTF-8 encoding, as this is the default Symfony encoding, and the one used by iconv to do the transliteration.

Also change the test file to run the test only if iconv is available:

Code Coverage

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 --coverage-html option:

Check the code coverage by opening the generated http://jobeet.local/cov/index.html page in a browser.
The code coverage only works if you have XDebug enabled and all dependencies installed. 

Your cov/index.html should look like this:

day 8 - code coverage1

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.

Doctrine Unit Tests

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.

At the beginning of this tutorial, we introduced the environments as a way to vary an application’s settings. By default, all symfony tests are run in the test environment, so let’s configure a different database for the test environment:

Go to your app/config directory and create a copy of parameters.yml file, called parameters_test.yml. Open parameters_test.yml and change the name of your database to jobeet_test. For this to be imported, we have to add it in the config_test.yml file :

Testing the Job Entity

First, we need to create the JobTest.php file in the Tests/Entity folder.

The setUp 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.

Testing the Repository Classes

Now, let’s write some tests for the JobRepository class, to see if the functions we created in the previous days are returning the right values:

We will do the same thing for CategoryRepository class:

After you finish writing the tests, run them with the following command, in order to generate the code coverage percent for the whole functions :

Now, if you go to http://jobeet.local/cov/Repository.html you will see that the code coverage for Repository Tests is not 100% complete.

Day 8 - coverage not complete

Let’s add some tests for the JobRepository 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 $max and $offset 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 foreach statement, from your testGetActiveJobs() function:

Run the code coverage command again :

This time, if you check your code coverage, you will see that it 100% complete.

Day 8 - coverage complete

That’s all for today! See you tomorrow, when we will talk about functional tests.

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.