Symfony 2.8 Jobeet Day 8: The Unit Tests

Tests in Symfony 2

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 functional tests.

Symfony integrates with an independent library, the PHPUnit, to give you a rich testing framework. It’s recommended to use the latest stable PHPUnit version, installed as PHAR:

chmod +x phpunit-6.1.phar
sudo mv phpunit-6.1.phar /usr/local/bin/phpunit
phpunit --version

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:

phpunit -c app/

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.

Unit Tests

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/AppBundle/Tests/Utils folder. By convention, the Tests/ sub-directory 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:

phpunit -c app/ src/AppBundle/Tests/Utils/JobeetTest

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

PHPUnit 6.1.4 by Sebastian Bergmann and contributors.
.                                                                   1 / 1 (100%)
Time: 110 ms, Memory: 8.00MB
OK (1 test, 6 assertions)

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:

$this->assertEquals('n-a', Jobeet::slugify(''));

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

PHPUnit 6.1.4 by Sebastian Bergmann and contributors.
F                                                                   1 / 1 (100%)
Time: 108 ms, Memory: 8.00MB
There was 1 failure:
1) AppBundle\Tests\Utils\JobeetTest::testSlugify
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
Tests: 1, Assertions: 7, Failures: 1.

Now, edit the Jobeet class 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:

$this->assertEquals('n-a', Jobeet::slugify(' - '));

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”:

$this->assertEquals('developpeur-web', Jobeet::slugify('Développeur Web'));

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:

That’s all for today, see you next time when we will write functional tests for our app. As always, the code form this post is available here:

About the Author

Passionate about web & mobile development. Doing this at IntelligentBee for the last 5 years. If you are looking for custom web and mobile app development, please contact us here and let’s have a talk.