Having taken part in testing more than 50 projects, we have seen how tests can give confidence in the code base, begin to save time for the entire development team and help to comply with business requirements.
For those who come from other ecosystems, let us first explain what the term “functional tests” means in Symfony. The following definition is given in the documentation:
“Functional tests verify the integration of different levels of the application (from routing to views).”
In essence, these are end-to-end tests. You write code that sends HTTP requests to the application. You get an HTTP response and make assumptions based on it. However, you can contact the database and make changes to the data storage level, which sometimes gives additional opportunities for checking the status code.
Today, we have prepared ten tips on writing functional tests with Symfony based on our experience.
In this article:
What is Symfony?
Symfony is a high-performance framework written in PHP. The framework is based on the Model-View-Controller pattern, and its architecture heavily uses other object-oriented programming patterns.
Symfony 2 works with PHP version 5.3.2 or higher. It supporst many databases (MySQL, MariaDB, PostgreSQL, SQLite, and other PDO-compatible DBMSs are also suitable). The relational database information in the project must be linked to the object model using an ORM tool. The base version of Symfony comes with two ORMs: Propel and Doctrine.
Of course, when choosing Symfony, rather than CMS, as a development platform, you should take into account the fact that the speed of creating typical solutions will be lower than using ready-made CMS modules. Yes, modules written in the framework will work faster and fully comply with business tasks, but their development will still cost more.
What are Functional Tests?
Functional tests are a powerful tool for testing your application from start to finish, from the moment a request is made in the browser to the moment the server's response arrives. They test all application layers: routing, model, actions, and templates. This is very similar to what you have already done manually more than once: each time you make changes to the code, you open the browser and see if everything works and if the correct page is opened by clicking on the link if all elements are displayed on the page. In other words, you were running the intended use cases for the newly added features of the application.
Since everything is done by hand, the process is boring and error-prone. Every time you change something, you should go through all the scripts and make sure your changes don't break anything. This is madness. Functional tests in Symfony offer a way to describe scenarios easily. Each test can be automatically played over and over again, emulating user actions in the browser. Like unit tests, they give you confidence that everything is in order with the code.
By the way...
Do you know how to conduct incident response planning?
In this article, we will tell you what would happen with your system if you're not responding to incidents properly and how to plan this activity correctlyLet's see
10 testing tips in Symfony
1. Testing with data storage tier
Probably the first thing you want to do when running functional tests is to separate the test base from the development base. This is done to create a clean bench for running tests and allows you to create and manage the desired state of the application. Besides, tests shouldn't write random data to a copy of the development database.
Typically, when running tests, a Symfony application is connected to another database. If you have Symfony 4 or 5, then you can define environment variables in the .env.test file that will be used for testing. Also, configure PHPUnit to change the environment variable APP_ENV to test. Fortunately, this happens by default when you install the Symfony component of PHPUnit Bridge.
With everything that is below version 4, you can use kernel booting in test mode when you run functional tests. Using the config_test.yml files, you can define your test configuration.
This package contains some important supporting tools for doing Symfony testing. Sometimes it tries to do too much and can interfere, but overall facilitates the work.
For example, during testing, you can emulate user input, upload fixture data, count database queries to check regression performance, etc. We recommend installing this package at the beginning of testing a new Symfony application.
3. Cleaning the database after each test
When do I need to clean the base during testing? The Symfony toolkit does not give a hint. We prefer to update the test database after each test method. The test suite looks like this:
class SomeControllerTest extends TestCase
public function test_a_registered_user_can_login()
// Clean slate. Database is empty.
// Create your world. Create users, roles and data.
// Execute logic.
// Assert the outcome.
// Database is reset.
A great way to clean up a database is to load empty fixtures into a special setUp method in PHPUnit. You can do this if you have installed LiipFunctionalTestBundle.
class BaseTestCase extends PHPUnit_Test_Case
public function setUp()
Thank you for Subscription!
4. Data creation
If you start each test with an empty database, then you need to have several utilities to create test data. These can be database creation models or entity objects.
Laravel has a very simple method with model factories. We try to follow the same approach and make interfaces that create objects that we often use in test. Here is an example of a simple interface that creates User entities:
public function makeUser(): User
$user = new User();
We can add such interfaces to the desired test suite:
class SomeControllerTest extends TestCase
public function test_a_registered_user_can_login()
$user = $this->createUser();
// Login as a user. Do some tests.
5. Replacing services in a container
In a Laravel application, it is very easy to swap services in a container, but in Symfony projects it is more difficult. In versions of Symfony 3.4 - 4.1, services in the container are marked as private. This means that while writing tests, you simply cannot access the service in the container and cannot specify another service (stub).
Although some argue that functional tests do not need to use stubs, there may be situations where you do not have a sandbox for third-party services and you do not want to give them random test data.
Fortunately, in Symfony 4.1, you can access the container and change services as you wish. For example:
class PaymentControllerTest extends BaseTestCase
public function test_a_user_can_purchase_product()
$paymentProcessorClient = $this->createMock(PaymentProcessorClient::class);
// this is a hack to make the container use the mocked instance after the redirects
But note that during functional testing, the Symfony core can boot a couple of times during the test, reassembling all the dependencies and discarding your stub.
6. SQLite execution in memory
SQLite is often used as a data storage tier when testing because it is very compact and very easy to configure. These qualities also make it very convenient to use in CI / CD environments.
SQLite is serverless, that is, the program will write and read all the data from the file. Surely this will become a bottleneck in terms of performance because I / O operations are added, the completion of which will have to wait. Therefore, you can use the in-memory option. Then the data will be written and read from memory, which can speed up operations.
When configuring the testing database in the Symfony application, you do not need to specify the database.sqlite file, just transfer it with the keyword: memory :.
7. In-memory SQLite execution with tmpfs
Working in memory is great, but sometimes it can be very difficult to configure this mode with the old version of LiipFunctionalTestBundle. If you also come across this, then there is such a trick that you can use.
On Linux systems, you can allocate part of the RAM, which will behave like normal storage. This is called tmpfs. In essence, you create a tmpfs folder, put the SQLite file in it, and use it to run the tests.
You can use the same approach with MySQL, however, the setup will be more complicated.
8. Elasticsearch Level Testing
As with connecting to a test instance of a database, you can connect to a test instance of Elasticsearch. Or even better: you can use other index names to separate development and test environments this way.
Testing Elasticsearch seems simple, but in practice it can be difficult. We have powerful tools for generating database schemes, creating fixtures, and filling databases with test data. These tools may not exist when it comes to Elasticsearch, and you have to create your solutions. That is, one does not simply start testing.
There is also the problem of indexing new data and ensuring the availability of information. A common mistake is the Elasticsearch update interval. Typically, indexed documents become searchable after a time specified in the configuration. By default, this is 1 second and it can become a plug for your tests if you are not careful.
9. Using Xdebug Filters to Speed Up Coverage Reporting
Coverage is an important aspect of testing. No need to treat it as a prime, it helps to find untested branches and threads in the source code.
Typically, Xdebug is responsible for evaluating coverage.
You will see that starting coverage analysis degrades the speed of the tests. This can be a problem in a CI / CD environment where every minute costs money.
Fortunately, some optimizations can be made. When Xdebug generates a coverage report of running tests, it does this for every PHP file in the test. This includes files located in the vendor’s folder - a code that does not belong to us.
By setting up code coverage filters in Xdebug, we can stop it from generating reports on files that we don’t need, saving a lot of time.
How to create filters? This can be done by PHPUnit. Only one command is needed to create a filter configuration file:
phpunit --dump-xdebug-filter build/xdebug-filter.php
And then we pass this config when running the tests:
phpunit --prepend build/xdebug-filter.php --coverage-html build/coverage-report
Do you know?
Fiddler Web Debugger: how to become a pro
Become an expert in using Fiddler with our tips based on real experience. Only essntial information from Geniusee specialistsShow me
10. Parallelization tools
Running functional tests can take a lot of time. For example, a full run of a set of 77 tests and 524 assumptions may take 3-4 minutes. This is normal, considering that each test makes a bunch of queries to the database, generates clusters of patterns, runs crawlers on these patterns, and makes certain assumptions.
If you open the activity monitor, you will see that the tests use only one core of your computer. You can use other kernels using parallelization tools like paratest and fastest. They are easy to configure to run unit tests, and if you need database connections, you may need to tinker.
In this article, we’ve gone through some of the basics of automated testing for web apps. We’ve used Symfony as a vehicle for conveying that information and, as such, learned a little about what it takes to build a multifaceted test suite with that framework.
We hope you find these tips useful. If you have any questions — fill the contact us form.