Testing

Whenever you write a new line of code, you also potentially add new bugs.To build better and more reliable applications, you should test your codeusing both functional and unit tests.

The PHPUnit Testing Framework

Symfony integrates with an independent library called PHPUnit to give you arich testing framework. This article won't cover PHPUnit itself, which has itsown excellent documentation.

Before creating your first test, install the PHPUnit Bridge component, whichwraps the original PHPUnit binary to provide additional features:

  1. $ composer require --dev symfony/phpunit-bridge

Each test - whether it's a unit test or a functional test - is a PHP classthat should live in the tests/ directory of your application. If you followthis rule, then you can run all of your application's tests with the followingcommand:

  1. $ ./bin/phpunit

Note

The ./bin/phpunit command is created by Symfony Flexwhen installing the phpunit-bridge package. If the command is missing, youcan remove the package (composer remove symfony/phpunit-bridge) and installit again. Another solution is to remove the project's symfony.lock file andrun composer install to force the execution of all Symfony Flex recipes.

PHPUnit is configured by the phpunit.xml.dist file in the root of yourSymfony application.

Tip

Code coverage can be generated with the —coverage-* options, see thehelp information that is shown when using —help for more information.

Unit Tests

A unit test is a test against a single PHP class, also called a unit. If youwant to test the overall behavior of your application, see the section aboutFunctional Tests.

Writing Symfony unit tests is no different from writing standard PHPUnitunit tests. Suppose, for example, that you have an incredibly simple classcalled Calculator in the src/Util/ directory of the app:

  1. // src/Util/Calculator.php
  2. namespace App\Util;
  3.  
  4. class Calculator
  5. {
  6. public function add($a, $b)
  7. {
  8. return $a + $b;
  9. }
  10. }

To test this, create a CalculatorTest file in the tests/Util directoryof your application:

  1. // tests/Util/CalculatorTest.php
  2. namespace App\Tests\Util;
  3.  
  4. use App\Util\Calculator;
  5. use PHPUnit\Framework\TestCase;
  6.  
  7. class CalculatorTest extends TestCase
  8. {
  9. public function testAdd()
  10. {
  11. $calculator = new Calculator();
  12. $result = $calculator->add(30, 12);
  13.  
  14. // assert that your calculator added the numbers correctly!
  15. $this->assertEquals(42, $result);
  16. }
  17. }

Note

By convention, the tests/ directory should replicate the directoryof your bundle for unit tests. So, if you're testing a class in thesrc/Util/ directory, put the test in the tests/Util/directory.

Just like in your real application - autoloading is automatically enabledvia the vendor/autoload.php file (as configured by default in thephpunit.xml.dist file).

You can also limit a test run to a directory or a specific test file:

  1. # run all tests of the application
  2. $ php bin/phpunit
  3.  
  4. # run all tests in the Util/ directory
  5. $ php bin/phpunit tests/Util
  6.  
  7. # run tests for the Calculator class
  8. $ php bin/phpunit tests/Util/CalculatorTest.php

Functional Tests

Functional tests check the integration of the different layers of anapplication (from the routing to the views). They are no different from unittests as far as PHPUnit is concerned, but they have a very specific workflow:

  • Make a request;
  • Click on a link or submit a form;
  • Test the response;
  • Rinse and repeat.Before creating your first test, install these packages that provide some of theutilities used in the functional tests:
  1. $ composer require --dev symfony/browser-kit symfony/css-selector

Your First Functional Test

Functional tests are PHP files that typically live in the tests/Controllerdirectory for your bundle. If you want to test the pages handled by yourPostController class, start by creating a new PostControllerTest.phpfile that extends a special WebTestCase class.

As an example, a test could look like this:

  1. // tests/Controller/PostControllerTest.php
  2. namespace App\Tests\Controller;
  3.  
  4. use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
  5.  
  6. class PostControllerTest extends WebTestCase
  7. {
  8. public function testShowPost()
  9. {
  10. $client = static::createClient();
  11.  
  12. $client->request('GET', '/post/hello-world');
  13.  
  14. $this->assertEquals(200, $client->getResponse()->getStatusCode());
  15. }
  16. }

Tip

To run your functional tests, the WebTestCase class needs to know whichis the application kernel to bootstrap it. The kernel class is usuallydefined in the KERNEL_CLASS environment variable (included in thedefault .env.test file provided by Symfony):

If your use case is more complex, you can also override thecreateKernel() or getKernelClass() methods of your functional test,which take precedence over the KERNEL_CLASS env var.

In the above example, you validated that the HTTP response was successful. Thenext step is to validate that the page actually contains the expected content.The createClient() method returns a client, which is like a browser thatyou'll use to crawl your site:

  1. $crawler = $client->request('GET', '/post/hello-world');

The request() method (readmore about the request method)returns a Crawler object which canbe used to select elements in the response, click on links and submit forms.

Tip

The Crawler only works when the response is an XML or an HTML document.To get the raw content response, call $client->getResponse()->getContent().

The crawler integrates with the symfony/css-selector component to give you thepower of CSS selectors to find content in a page. To install the CSS selectorcomponent, run:

  1. $ composer require --dev symfony/css-selector

Now you can use CSS selectors with the crawler. To assert that the phrase"Hello World" is present in the page's main title, you can use this assertion:

  1. $this->assertSelectorTextContains('html h1.title', 'Hello World');

This assertion will internally call $crawler->filter('html h1.title'), which allowsyou to use CSS selectors to filter any HTML element in the page and check forits existence, attributes, text, etc.

The assertSelectorTextContains method is not a native PHPUnit assertion and isavailable thanks to the WebTestCase class.

New in version 4.3: The WebTestCase assertions were introduced in Symfony 4.3

Using native PHPUnit methods, the same assertion would look like this:

  1. $this->assertGreaterThan(
  2. 0,
  3. $crawler->filter('html h1.title:contains("Hello World")')->count()
  4. );

The crawler can also be used to interact with the page. Click on a link by firstselecting it with the crawler using either an XPath expression or a CSS selector,then use the client to click on it:

  1. $link = $crawler
  2. ->filter('a:contains("Greet")') // find all links with the text "Greet"
  3. ->eq(1) // select the second link in the list
  4. ->link()
  5. ;
  6.  
  7. // and click it
  8. $crawler = $client->click($link);

Submitting a form is very similar: select a form button, optionally overridesome form values and submit the corresponding form:

  1. $form = $crawler->selectButton('submit')->form();
  2.  
  3. // set some values
  4. $form['name'] = 'Lucas';
  5. $form['form_name[subject]'] = 'Hey there!';
  6.  
  7. // submit the form
  8. $crawler = $client->submit($form);

Tip

The form can also handle uploads and contains methods to fill in different typesof form fields (e.g. select() and tick()). For details, see theForms section below.

Now that you can navigate through an application, use assertions to testthat it actually does what you expect it to. Use the Crawler to make assertionson the DOM:

  1. // asserts that the response matches a given CSS selector.
  2. $this->assertGreaterThan(0, $crawler->filter('h1')->count());

Or test against the response content directly if you just want to assert thatthe content contains some text or in case that the response is not an XML/HTMLdocument:

  1. $this->assertContains(
  2. 'Hello World',
  3. $client->getResponse()->getContent()
  4. );

Tip

Instead of installing each testing dependency individually, you can use thetest Symfony pack to install all those dependencies at once:

  1. $ composer require --dev symfony/test-pack

Useful Assertions

To get you started faster, here is a list of the most common anduseful test assertions:

  1. use Symfony\Component\HttpFoundation\Response;
  2.  
  3. // ...
  4.  
  5. // asserts that there is at least one h2 tag
  6. // with the class "subtitle"
  7. $this->assertGreaterThan(
  8. 0,
  9. $crawler->filter('h2.subtitle')->count()
  10. );
  11.  
  12. // asserts that there are exactly 4 h2 tags on the page
  13. $this->assertCount(4, $crawler->filter('h2'));
  14.  
  15. // asserts that the "Content-Type" header is "application/json"
  16. $this->assertTrue(
  17. $client->getResponse()->headers->contains(
  18. 'Content-Type',
  19. 'application/json'
  20. ),
  21. 'the "Content-Type" header is "application/json"' // optional message shown on failure
  22. );
  23.  
  24. // asserts that the response content contains a string
  25. $this->assertContains('foo', $client->getResponse()->getContent());
  26. // ...or matches a regex
  27. $this->assertRegExp('/foo(bar)?/', $client->getResponse()->getContent());
  28.  
  29. // asserts that the response status code is 2xx
  30. $this->assertTrue($client->getResponse()->isSuccessful(), 'response status is 2xx');
  31. // asserts that the response status code is 404
  32. $this->assertTrue($client->getResponse()->isNotFound());
  33. // asserts a specific 200 status code
  34. $this->assertEquals(
  35. 200, // or Symfony\Component\HttpFoundation\Response::HTTP_OK
  36. $client->getResponse()->getStatusCode()
  37. );
  38.  
  39. // asserts that the response is a redirect to /demo/contact
  40. $this->assertTrue(
  41. $client->getResponse()->isRedirect('/demo/contact')
  42. // if the redirection URL was generated as an absolute URL
  43. // $client->getResponse()->isRedirect('http://localhost/demo/contact')
  44. );
  45. // ...or simply check that the response is a redirect to any URL
  46. $this->assertTrue($client->getResponse()->isRedirect());

Testing against Different Sets of Data

It's common to have to execute the same test against different sets of data tocheck the multiple conditions code must handle. This is solved with PHPUnit'sdata providers, which work both for unit and functional tests.

First, add one or more arguments to your test method and use them inside thetest code. Then, define another method which returns a nested array with thearguments to use on each test run. Lastly, add the @dataProvider annotationto associate both methods:

  1. /**
  2. * @dataProvider provideUrls
  3. */
  4. public function testPageIsSuccessful($url)
  5. {
  6. $client = self::createClient();
  7. $client->request('GET', $url);
  8.  
  9. $this->assertTrue($client->getResponse()->isSuccessful());
  10. }
  11.  
  12. public function provideUrls()
  13. {
  14. return [
  15. ['/'],
  16. ['/blog'],
  17. ['/contact'],
  18. // ...
  19. ];
  20. }

Working with the Test Client

The test client simulates an HTTP client like a browser and makes requestsinto your Symfony application:

  1. $crawler = $client->request('GET', '/post/hello-world');

The request() method takes the HTTP method and a URL as arguments andreturns a Crawler instance.

Tip

Hardcoding the request URLs is a best practice for functional tests. If thetest generates URLs using the Symfony router, it won't detect any changemade to the application URLs which may impact the end users.

More about the request() Method:

The full signature of the request() method is:

  1. request(
  2. $method,
  3. $uri,
  4. array $parameters = [],
  5. array $files = [],
  6. array $server = [],
  7. $content = null,
  8. $changeHistory = true
  9. )

The server array is the raw values that you'd expect to normallyfind in the PHP $_SERVER superglobal. For example, to set theContent-Type and Referer HTTP headers, you'd pass the following (mindthe HTTP_ prefix for non standard headers):

  1. $client->request(
  2. 'GET',
  3. '/post/hello-world',
  4. [],
  5. [],
  6. [
  7. 'CONTENT_TYPE' => 'application/json',
  8. 'HTTP_REFERER' => '/foo/bar',
  9. ]
  10. );

Use the crawler to find DOM elements in the response. These elements can thenbe used to click on links and submit forms:

  1. $crawler = $client->clickLink('Go elsewhere...');
  2.  
  3. $crawler = $client->submitForm('validate', ['name' => 'Fabien']);

The clickLink() and submitForm() methods both return a Crawler object.These methods are the best way to browse your application as it takes careof a lot of things for you, like detecting the HTTP method from a form andgiving you a nice API for uploading files.

The request() method can also be used to simulate form submissions directlyor perform more complex requests. Some useful examples:

  1. // submits a form directly (but using the Crawler is easier!)
  2. $client->request('POST', '/submit', ['name' => 'Fabien']);
  3.  
  4. // submits a raw JSON string in the request body
  5. $client->request(
  6. 'POST',
  7. '/submit',
  8. [],
  9. [],
  10. ['CONTENT_TYPE' => 'application/json'],
  11. '{"name":"Fabien"}'
  12. );
  13.  
  14. // Form submission with a file upload
  15. use Symfony\Component\HttpFoundation\File\UploadedFile;
  16.  
  17. $photo = new UploadedFile(
  18. '/path/to/photo.jpg',
  19. 'photo.jpg',
  20. 'image/jpeg',
  21. null
  22. );
  23. $client->request(
  24. 'POST',
  25. '/submit',
  26. ['name' => 'Fabien'],
  27. ['photo' => $photo]
  28. );
  29.  
  30. // Perform a DELETE request and pass HTTP headers
  31. $client->request(
  32. 'DELETE',
  33. '/post/12',
  34. [],
  35. [],
  36. ['PHP_AUTH_USER' => 'username', 'PHP_AUTH_PW' => 'pa$$word']
  37. );

Last but not least, you can force each request to be executed in its own PHPprocess to avoid any side effects when working with several clients in the samescript:

  1. $client->insulate();

AJAX Requests

The Client provides a xmlHttpRequest()method, which has the same arguments as the request() method, and it's ashortcut to make AJAX requests:

  1. // the required HTTP_X_REQUESTED_WITH header is added automatically
  2. $client->xmlHttpRequest('POST', '/submit', ['name' => 'Fabien']);

Browsing

The Client supports many operations that can be done in a real browser:

  1. $client->back();
  2. $client->forward();
  3. $client->reload();
  4.  
  5. // clears all cookies and the history
  6. $client->restart();

Note

The back() and forward() methods skip the redirects that may haveoccurred when requesting a URL, as normal browsers do.

Accessing Internal Objects

If you use the client to test your application, you might want to access theclient's internal objects:

  1. $history = $client->getHistory();
  2. $cookieJar = $client->getCookieJar();

You can also get the objects related to the latest request:

  1. // the HttpKernel request instance
  2. $request = $client->getRequest();
  3.  
  4. // the BrowserKit request instance
  5. $request = $client->getInternalRequest();
  6.  
  7. // the HttpKernel response instance
  8. $response = $client->getResponse();
  9.  
  10. // the BrowserKit response instance
  11. $response = $client->getInternalResponse();
  12.  
  13. $crawler = $client->getCrawler();

Accessing the Container

It's highly recommended that a functional test only tests the response. Butunder certain very rare circumstances, you might want to access some servicesto write assertions. Given that services are private by default, test classesdefine a property that stores a special container created by Symfony whichallows fetching both public and all non-removed private services:

  1. // gives access to the same services used in your test, unless you're using
  2. // $client->insulate() or using real HTTP requests to test your application
  3. $container = self::$container;

For a list of services available in your application, use the debug:containercommand.

Tip

The special container that gives access to private services exists only inthe test environment and is itself a service that you can get from thereal container using the test.service_container id.

Tip

If the information you need to check is available from the profiler, useit instead.

Accessing the Profiler Data

On each request, you can enable the Symfony profiler to collect data about theinternal handling of that request. For example, the profiler could be used toverify that a given page executes less than a certain number of databasequeries when loading.

To get the Profiler for the last request, do the following:

  1. // enables the profiler for the very next request
  2. $client->enableProfiler();
  3.  
  4. $crawler = $client->request('GET', '/profiler');
  5.  
  6. // gets the profile
  7. $profile = $client->getProfile();

For specific details on using the profiler inside a test, see theHow to Use the Profiler in a Functional Test article.

Redirecting

When a request returns a redirect response, the client does not followit automatically. You can examine the response and force a redirectionafterwards with the followRedirect() method:

  1. $crawler = $client->followRedirect();

If you want the client to automatically follow all redirects, you canforce them by calling the followRedirects() method before performing the request:

  1. $client->followRedirects();

If you pass false to the followRedirects() method, the redirectswill no longer be followed:

  1. $client->followRedirects(false);

Reporting Exceptions

Debugging exceptions in functional tests may be difficult because by defaultthey are caught and you need to look at the logs to see which exception wasthrown. Disabling catching of exceptions in the test client allows the exceptionto be reported by PHPUnit:

  1. $client->catchExceptions(false);

The Crawler

A Crawler instance is returned each time you make a request with the Client.It allows you to traverse HTML documents, select nodes, find links and forms.

Traversing

Like jQuery, the Crawler has methods to traverse the DOM of an HTML/XMLdocument. For example, the following finds all input[type=submit] elements,selects the last one on the page, and then selects its immediate parent element:

  1. $newCrawler = $crawler->filter('input[type=submit]')
  2. ->last()
  3. ->parents()
  4. ->first()
  5. ;

Many other methods are also available:

  • filter('h1.title')
  • Nodes that match the CSS selector.
  • filterXpath('h1')
  • Nodes that match the XPath expression.
  • eq(1)
  • Node for the specified index.
  • first()
  • First node.
  • last()
  • Last node.
  • siblings()
  • Siblings.
  • nextAll()
  • All following siblings.
  • previousAll()
  • All preceding siblings.
  • parents()
  • Returns the parent nodes.
  • children()
  • Returns children nodes.
  • reduce($lambda)
  • Nodes for which the callable does not return false.Since each of these methods returns a new Crawler instance, you cannarrow down your node selection by chaining the method calls:
  1. $crawler
  2. ->filter('h1')
  3. ->reduce(function ($node, $i) {
  4. if (!$node->attr('class')) {
  5. return false;
  6. }
  7. })
  8. ->first()
  9. ;

Tip

Use the count() function to get the number of nodes stored in a Crawler:count($crawler)

Extracting Information

The Crawler can extract information from the nodes:

  1. // returns the attribute value for the first node
  2. $crawler->attr('class');
  3.  
  4. // returns the node value for the first node
  5. $crawler->text();
  6.  
  7. // extracts an array of attributes for all nodes
  8. // (_text returns the node value)
  9. // returns an array for each element in crawler,
  10. // each with the value and href
  11. $info = $crawler->extract(['_text', 'href']);
  12.  
  13. // executes a lambda for each node and return an array of results
  14. $data = $crawler->each(function ($node, $i) {
  15. return $node->attr('href');
  16. });

Use the clickLink() method to click on the first link that contains thegiven text (or the first clickable image with that alt attribute):

  1. $client = static::createClient();
  2. $client->request('GET', '/post/hello-world');
  3.  
  4. $client->clickLink('Click here');

If you need access to the Link objectthat provides helpful methods specific to links (such as getMethod() andgetUri()), use the selectLink() method instead:

  1. $client = static::createClient();
  2. $crawler = $client->request('GET', '/post/hello-world');
  3.  
  4. $link = $crawler->selectLink('Click here')->link();
  5. $client->click($link);

Forms

Use the submitForm() method to submit the form that contains the given button:

  1. $client = static::createClient();
  2. $client->request('GET', '/post/hello-world');
  3.  
  4. $crawler = $client->submitForm('Add comment', [
  5. 'comment_form[content]' => '...',
  6. ]);

The first argument of submitForm() is the text content, id, value orname of any <button> or <input type="submit"> included in the form.The second optional argument is used to override the default form field values.

Note

Notice that you select form buttons and not forms as a form can have severalbuttons; if you use the traversing API, keep in mind that you must look for abutton.

If you need access to the Form objectthat provides helpful methods specific to forms (such as getUri(),getValues() and getFields()) use the selectButton() method instead:

  1. $client = static::createClient();
  2. $crawler = $client->request('GET', '/post/hello-world');
  3.  
  4. $buttonCrawlerNode = $crawler->selectButton('submit');
  5.  
  6. // select the form that contains this button
  7. $form = $buttonCrawlerNode->form();
  8.  
  9. // you can also pass an array of field values that overrides the default ones
  10. $form = $buttonCrawlerNode->form([
  11. 'my_form[name]' => 'Fabien',
  12. 'my_form[subject]' => 'Symfony rocks!',
  13. ]);
  14.  
  15. // you can pass a second argument to override the form HTTP method
  16. $form = $buttonCrawlerNode->form([], 'DELETE');
  17.  
  18. // submit the Form object
  19. $client->submit($form);

The field values can also be passed as a second argument of the submit()method:

  1. $client->submit($form, [
  2. 'my_form[name]' => 'Fabien',
  3. 'my_form[subject]' => 'Symfony rocks!',
  4. ]);

For more complex situations, use the Form instance as an array to set thevalue of each field individually:

  1. // changes the value of a field
  2. $form['my_form[name]'] = 'Fabien';
  3. $form['my_form[subject]'] = 'Symfony rocks!';

There is also a nice API to manipulate the values of the fields according totheir type:

  1. // selects an option or a radio
  2. $form['country']->select('France');
  3.  
  4. // ticks a checkbox
  5. $form['like_symfony']->tick();
  6.  
  7. // uploads a file
  8. $form['photo']->upload('/path/to/lucas.jpg');
  9.  
  10. // In the case of a multiple file upload
  11. $form['my_form[field][O]']->upload('/path/to/lucas.jpg');
  12. $form['my_form[field][1]']->upload('/path/to/lisa.jpg');

Tip

If you purposefully want to select "invalid" select/radio values, seeSelecting Invalid Choice Values.

Tip

You can get the values that will be submitted by calling the getValues()method on the Form object. The uploaded files are available in aseparate array returned by getFiles(). The getPhpValues() andgetPhpFiles() methods also return the submitted values, but in thePHP format (it converts the keys with square brackets notation - e.g.my_form[subject] - to PHP arrays).

Tip

The submit() and submitForm() methods define optional arguments toadd custom server parameters and HTTP headers when submitting the form:

  1. $client->submit($form, [], ['HTTP_ACCEPT_LANGUAGE' => 'es']);
  2. $client->submitForm($button, [], 'POST', ['HTTP_ACCEPT_LANGUAGE' => 'es']);

Adding and Removing Forms to a Collection

If you use a Collection of Forms,you can't add fields to an existing form with$form['task[tags][0][name]'] = 'foo';. This results in an errorUnreachable field "…" because $form can only be used in order toset values of existing fields. In order to add new fields, you have toadd the values to the raw data array:

  1. // gets the form
  2. $form = $crawler->filter('button')->form();
  3.  
  4. // gets the raw values
  5. $values = $form->getPhpValues();
  6.  
  7. // adds fields to the raw values
  8. $values['task']['tags'][0]['name'] = 'foo';
  9. $values['task']['tags'][1]['name'] = 'bar';
  10.  
  11. // submits the form with the existing and new values
  12. $crawler = $client->request($form->getMethod(), $form->getUri(), $values,
  13. $form->getPhpFiles());
  14.  
  15. // the 2 tags have been added to the collection
  16. $this->assertEquals(2, $crawler->filter('ul.tags > li')->count());

Where task[tags][0][name] is the name of a field createdwith JavaScript.

You can remove an existing field, e.g. a tag:

  1. // gets the values of the form
  2. $values = $form->getPhpValues();
  3.  
  4. // removes the first tag
  5. unset($values['task']['tags'][0]);
  6.  
  7. // submits the data
  8. $crawler = $client->request($form->getMethod(), $form->getUri(),
  9. $values, $form->getPhpFiles());
  10.  
  11. // the tag has been removed
  12. $this->assertEquals(0, $crawler->filter('ul.tags > li')->count());

Testing Configuration

The Client used by functional tests creates a Kernel that runs in a specialtest environment. Since Symfony loads the config/packages/test/*.yamlin the test environment, you can tweak any of your application's settingsspecifically for testing.

For example, by default, the Swift Mailer is configured to not actuallydeliver emails in the test environment. You can see this under the swiftmailerconfiguration option:

  • YAML
  1. # config/packages/test/swiftmailer.yaml
  2.  
  3. # ...
  4. swiftmailer:
  5. disable_delivery: true
  • XML
  1. <!-- config/packages/test/swiftmailer.xml -->
  2. <?xml version="1.0" encoding="UTF-8" ?>
  3. <container xmlns="http://symfony.com/schema/dic/services"
  4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  5. xmlns:swiftmailer="http://symfony.com/schema/dic/swiftmailer"
  6. xsi:schemaLocation="http://symfony.com/schema/dic/services
  7. https://symfony.com/schema/dic/services/services-1.0.xsd
  8. http://symfony.com/schema/dic/swiftmailer
  9. https://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd">
  10.  
  11. <!-- ... -->
  12. <swiftmailer:config disable-delivery="true"/>
  13. </container>
  • PHP
  1. // config/packages/test/swiftmailer.php
  2.  
  3. // ...
  4. $container->loadFromExtension('swiftmailer', [
  5. 'disable_delivery' => true,
  6. ]);

You can also use a different environment entirely, or override the defaultdebug mode (true) by passing each as options to the createClient()method:

  1. $client = static::createClient([
  2. 'environment' => 'my_test_env',
  3. 'debug' => false,
  4. ]);

Customizing Database URL / Environment Variables

If you need to customize some environment variables for your tests (e.g. theDATABASE_URL used by Doctrine), you can do that by overriding anything youneed in your .env.test file:

  1. # .env.test
  2. DATABASE_URL="mysql://db_user:[email protected]:3306/db_name_test"
  3.  
  4. # use SQLITE
  5. # DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db"

This file is automatically read in the test environment: any keys here overridethe defaults in .env.

Caution

Applications created before November 2018 had a slightly different system,involving a .env.dist file. For information about upgrading, see:Nov 2018 Changes to .env & How to Update.

Sending Custom Headers

If your application behaves according to some HTTP headers, pass them as thesecond argument of createClient():

  1. $client = static::createClient([], [
  2. 'HTTP_HOST' => 'en.example.com',
  3. 'HTTP_USER_AGENT' => 'MySuperBrowser/1.0',
  4. ]);

You can also override HTTP headers on a per request basis:

  1. $client->request('GET', '/', [], [], [
  2. 'HTTP_HOST' => 'en.example.com',
  3. 'HTTP_USER_AGENT' => 'MySuperBrowser/1.0',
  4. ]);

Tip

The test client is available as a service in the container in the testenvironment (or wherever the framework.testoption is enabled). This means you can override the service entirelyif you need to.

PHPUnit Configuration

Each application has its own PHPUnit configuration, stored in thephpunit.xml.dist file. You can edit this file to change the defaults orcreate a phpunit.xml file to set up a configuration for your local machineonly.

Tip

Store the phpunit.xml.dist file in your code repository and ignorethe phpunit.xml file.

By default, only the tests stored in tests/ are run via the phpunit command,as configured in the phpunit.xml.dist file:

  1. <!-- phpunit.xml.dist -->
  2. <phpunit>
  3. <!-- ... -->
  4. <testsuites>
  5. <testsuite name="Project Test Suite">
  6. <directory>tests</directory>
  7. </testsuite>
  8. </testsuites>
  9. <!-- ... -->
  10. </phpunit>

But you can add more directories. For instance, the followingconfiguration adds tests from a custom lib/tests directory:

  1. <!-- phpunit.xml.dist -->
  2. <phpunit>
  3. <!-- ... -->
  4. <testsuites>
  5. <testsuite name="Project Test Suite">
  6. <!-- ... --->
  7. <directory>lib/tests</directory>
  8. </testsuite>
  9. </testsuites>
  10. <!-- ... -->
  11. </phpunit>

To include other directories in the code coverage, also edit the <filter>section:

  1. <!-- phpunit.xml.dist -->
  2. <phpunit>
  3. <!-- ... -->
  4. <filter>
  5. <whitelist>
  6. <!-- ... -->
  7. <directory>lib</directory>
  8. <exclude>
  9. <!-- ... -->
  10. <directory>lib/tests</directory>
  11. </exclude>
  12. </whitelist>
  13. </filter>
  14. <!-- ... -->
  15. </phpunit>

Learn more