Functional Tests

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

The last type of test is called a functional test. And it's way different than what we've seen so far. With unit and integration tests, we call methods on our code and test the output. But with a functional test, you command a browser, which surfs to your site, clicks on links, fills out forms and asserts things it sees on the page. Yep, you're testing the interface that your users actually use.

Oh, and functional tests also apply if you're building an API. It's the same idea: you would use an HTTP client to make real HTTP requests to your API and assert the output.


First, to give us some magic, I want to install a special bundle: Google for LiipFunctionalTestBundle.

This bundle is not needed to write functional tests. But, it has a collection of optional, extra goodies!

Copy the composer require line, move over to your terminal, and paste:

composer require --dev liip/functional-test-bundle


If you are using PHPUnit 7+ or Symfony 4, you need to require version 2.0 of this bundle. At this time, 2.0 is still alpha, and needs to be installed specifically with: composer require --dev liip/functional-test-bundle:~2.0@alpha

If you're using Symfony 3, make sure you've installed PHPUnit 6.3 and install version 1 of this bundle so that everything can play together nicely: composer require --dev liip/functional-test-bundle:^1.9

Functional Test Setup

Functional tests look like unit tests at first: they use PHPUnit in the exact way we've been seeing. But instead of writing one test class per PHP class, you'll usually create one test class per controller class.

It doesn't have much yet, but we're going to functionally test our homepage. Since the code behind this lives in DefaultController, let's create a Controller directory in tests and add a new DefaultControllerTest class.

But now, instead of extending TestCase or KernelTestCase, extend WebTestCase. But wait! There are two! The normal base class is the one from FrameworkBundle. It actually extends KernelTestCase, which means we have all the same tools as integration tests. But, it adds a few methods to help create a client object: a special object we'll use to make requests into our app.

Today we'll choose WebTestCase from LiipFunctionalTestBundle. No surprise, this class itself extends the normal WebTestCase. Then, it adds a bunch of optional magic.

... lines 1 - 4
use Liip\FunctionalTestBundle\Test\WebTestCase;
class DefaultControllerTest extends WebTestCase
... lines 9 - 16

TDD & The Functional Test

Let's add the first test: public function testEnclosuresAreShownOnTheHomepage(). Right now, the homepage is empty. But in a minute, we're going to render all of the enclosures. So let's do a little TDD testing! Start by creating a client with $client = $this->makeClient(). This method comes from LiipFunctionalTestBundle, but is just a wrapper around Symfony's normal static::createClient(). The version from the bundle just adds some optional authentication magic.

... lines 1 - 6
class DefaultControllerTest extends WebTestCase
public function testEnclosuresAreShownOnHomepage()
... line 10
$client = $this->makeClient();
... lines 12 - 15

Next, make a request! $crawler = $client->request('GET', '/') to go to the homepage. We'll talk more about this Crawler object in a few minutes. Then, the simplest test is to say $this->assertStatusCode(200) and pass $client. But even this is just a shortcut to make sure 200 matches $client->getResponse()->getStatusCode().

... lines 1 - 8
public function testEnclosuresAreShownOnHomepage()
... lines 10 - 12
$crawler = $client->request('GET', '/');
$this->assertStatusCode(200, $client);
... lines 17 - 18

And yea... the first part of the test is done! This at least makes sure our page isn't broken!

Finishing Installing LiipFunctionalTestBundle

But before we try it, we need to finish installing the bundle. Copy the 3 bundle lines, open AppKernel and paste them there.

... lines 1 - 6
class AppKernel extends Kernel
public function registerBundles()
... lines 11 - 21
if (in_array($this->getEnvironment(), ['dev', 'test'], true)) {
... lines 23 - 31
if ('test' === $this->getEnvironment()) {
$bundles[] = new LiipFunctionalTestBundle();
... lines 36 - 37
... lines 39 - 58

We also need to add one line to config_test.yml.

... lines 1 - 27
liip_functional_test: ~

If you're using Symfony Flex, these steps should eventually be done for you. I say eventually, because - at this moment - Symfony 4 support is still being added to the bundle.

Ok! Let's try it! Find your terminal, run phpunit, and point it at the new controller:

./vendor/bin/phpunit tests/AppBundle/Controller/DefaultControllerTest.php

Yes! It passes!

Symfony's Client Versus Mink Versus Others

But... what just happened exactly? Did our code just make a real HTTP request to our app... just like if we refreshed it in the browser? Well... not quite.

Remember: in a functional test, we use PHP to command a browser and tell it to go to a page, click on links and fill out forms. But, there are multiple libraries that give us this superpower. We're using Symfony's BrowserKit client... mostly because it's built into Symfony and easy to start using. But, there are others. My favorite is called Mink, which is used behind the scenes with Behat. We have a tutorial all about Behat, with big sections devoted to Mink. So, check that out.

Go Deeper!

Learn all about Mink and Behat:

So... what's the difference between Symfony's BrowserKit and Mink? With BrowserKit, you're not actually making a real HTTP request to your app. Nope, you're making a "fake" request directly into your code. Honestly, that doesn't matter much. And actually, it makes setup a bit easier: we didn't need to configure that our site lived at http://localhost:8000 or anything like that.

But, BrowserKit has one big disadvantage: you can't test JavaScript functionality. Nope, since these are fake requests, your JavaScript simply doesn't run! This is the main reason why I prefer Mink: it does allow you to run your code in a real browser... with JavaScript.

For the next few chapters, we are going to use Symfony's BrowserKit Client. But, most of the concepts transfer well to other test clients, like Mink. And you'll still be able to use most of the magic from LiipFuntionalTestBundle. If you have questions about this, just ask in the comments!

Next, let's talk about this $crawler object and how we can use it to dive into the HTML of the page!

Leave a comment!

  • 2019-02-04 Leanna Pelham

    Hey Nicolás González Flores ! Thanks again for reporting this issue, we've updated the script and added a note to the video. You rock! 👏

  • 2018-09-17 weaverryan

    Hey Nicolás González Flores!

    Thanks for the pointer on this! We'll look into adding a note so other people don't hit this :).


  • 2018-09-15 Nicolás González Flores

    Hello guys,

    in case you are using Symfony 3.3 and PHPUnit 7 or later (the version that will be installed if you follow this tutorial at this moment), you may end in a limbo of compatibility with liip. Make sure you are using the same version of PHPUnit that this tutorial is using, the version 6.3. Then you won't have any problem installing liip.


  • 2018-06-11 Diego Aguiar

    Usually you don't, but if you don't care about *that* service (maybe it's just a dependency that you are not exercicing in that test), then you can mock it out.

  • 2018-06-09 Coder

    Hi, so in functgional test we still mock some classes?

  • 2018-06-04 Diego Aguiar

    Hey Coder

    Good question, an end-to-end test is when you test the entire flow of a feature in your system, in other words, you test that all your components communicates as expected, in this kind of tests you don't mock anything, if you have to store something in the DB, you configure a test DB, if you have to hit an external resource like an API, first you have to find if they offer a sandbox endpoint, if not, then you would have to create a test account so you can use those credentials in your tests


  • 2018-06-02 Coder

    if when clicks buttons it is called functional test, then what is end to end test?