@@ -15,12 +15,13 @@ Symfony integrates with an independent library called `PHPUnit`_ to give
15
15
you a rich testing framework. This article won't cover PHPUnit itself,
16
16
which has its own excellent `documentation `_.
17
17
18
- Before creating your first test, install PHPUnit with the following
19
- command:
18
+ Before creating your first test, install ``phpunit/phpunit `` and the
19
+ ``symfony/test-pack ``, which installs some other packages providing useful
20
+ Symfony test utilities:
20
21
21
22
.. code-block :: terminal
22
23
23
- $ composer require --dev phpunit/phpunit
24
+ $ composer require --dev phpunit/phpunit symfony/test-pack
24
25
25
26
After the library is installed, try running PHPUnit:
26
27
@@ -69,9 +70,9 @@ Unit Tests
69
70
70
71
A `unit test `_ ensures that individual units of source code (e.g. a single
71
72
class or some specific method in some class) meet their design and behave
72
- as intended. Writing unit tests in a Symfony application no different from
73
- writing standard PHPUnit unit tests. You can learn about it in the PHPUnit
74
- documentation: `Writing Tests for PHPUnit `_.
73
+ as intended. Writing unit tests in a Symfony application is no different
74
+ from writing standard PHPUnit unit tests. You can learn about it in the
75
+ PHPUnit documentation: `Writing Tests for PHPUnit `_.
75
76
76
77
By convention, the ``tests/ `` directory should replicate the directory
77
78
of your application for unit tests. So, if you're testing a class in the
@@ -95,7 +96,9 @@ You can run tests using the ``./vendor/bin/phpunit`` command:
95
96
.. tip ::
96
97
97
98
In large test suites, it can make sense to create subdirectories for
98
- each type of tests (e.g. ``tests/unit/ `` and ``test/functional/ ``).
99
+ each type of tests (e.g. ``tests/Unit/ `` and ``test/Functional/ ``).
100
+
101
+ .. _integration-tests :
99
102
100
103
Integration Tests
101
104
-----------------
@@ -118,7 +121,6 @@ class to help you creating and booting the kernel in your tests using
118
121
{
119
122
public function testSomething()
120
123
{
121
- // boot the Symfony kernel
122
124
self::bootKernel();
123
125
124
126
// ...
@@ -198,7 +200,15 @@ method::
198
200
.. tip ::
199
201
200
202
It is recommended to run your test with ``debug `` set to ``false `` on
201
- your CI server, as it significantly improves test performance.
203
+ your CI server, as it significantly improves test performance. This
204
+ disables clearing the cache. If your tests don't run in a clean
205
+ environment each time, you have to manually clear it using for instance
206
+ this code in ``tests/bootstrap.php ``::
207
+
208
+ // ...
209
+
210
+ // ensure a fresh cache when debug mode is disabled
211
+ (new \Symfony\Component\Filesystem\Filesystem())->remove(__DIR__.'/../var/cache/test');
202
212
203
213
Customizing Environment Variables
204
214
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -243,7 +253,7 @@ the container is stored in ``self::$container``::
243
253
{
244
254
public function testSomething()
245
255
{
246
- // boot the Symfony kernel
256
+ // (1) boot the Symfony kernel
247
257
self::bootKernel();
248
258
249
259
// (2) use self::$container to access the service container
@@ -267,6 +277,8 @@ It gives you access to both the public services and the non-removed
267
277
are not used by any other services), you need to declare those private
268
278
services as public in the ``config/services_test.yaml `` file.
269
279
280
+ .. _testing-databases :
281
+
270
282
Configuring a Database for Tests
271
283
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
272
284
@@ -410,21 +422,35 @@ have a very specific workflow:
410
422
#. Test the response;
411
423
#. Rinse and repeat.
412
424
413
- Before creating your first test, install the ``symfony/test-pack `` which
414
- requires multiple packages providing some of the utilities used in the
415
- tests:
416
-
417
- .. code-block :: terminal
425
+ .. note ::
418
426
419
- $ composer require --dev symfony/test-pack
427
+ The tools used in this section can be installed via the ``symfony/test-pack ``,
428
+ use ``composer require symfony/test-pack `` if you haven't done so already.
420
429
421
430
Write Your First Application Test
422
431
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
423
432
424
- Application tests are PHP files that typically live in the ``tests/Controller ``
425
- directory of your application. If you want to test the pages handled by your
426
- ``PostController `` class, start by creating a new ``PostControllerTest.php ``
427
- file that extends a special ``WebTestCase `` class::
433
+ Application tests are PHP files that typically live in the ``tests/Controller/ ``
434
+ directory of your application. They often extend
435
+ :class: `Symfony\\ Bundle\\ FrameworkBundle\\ Test\\ WebTestCase `. This class
436
+ adds special logic on top of the ``KernelTestCase ``. You can read more
437
+ about that in the above :ref: `section on integration tests <integration-tests >`.
438
+
439
+ If you want to test the pages handled by your
440
+ ``PostController `` class, start by creating a new ``PostControllerTest ``
441
+ using the ``make:test `` command of the `SymfonyMakerBundle `_:
442
+
443
+ .. code-block :: terminal
444
+
445
+ $ php bin/console make:test
446
+
447
+ Which test type would you like?:
448
+ > WebTestCase
449
+
450
+ The name of the test class (e.g. BlogPostTest):
451
+ > Controller\PostControllerTest
452
+
453
+ This creates the following test class::
428
454
429
455
// tests/Controller/PostControllerTest.php
430
456
namespace App\Tests\Controller;
@@ -433,87 +459,36 @@ file that extends a special ``WebTestCase`` class::
433
459
434
460
class PostControllerTest extends WebTestCase
435
461
{
436
- public function testShowPost()
462
+ public function testSomething(): void
437
463
{
464
+ // This calls KernelTestCase::bootKernel(), and creates a
465
+ // "client" that is acting as the browser
438
466
$client = static::createClient();
439
467
440
- $client->request('GET', '/post/hello-world');
468
+ // Request a specific page
469
+ $crawler = $client->request('GET', '/');
441
470
442
- $this->assertEquals(200, $client->getResponse()->getStatusCode());
471
+ // Validate a successful response and some content
472
+ $this->assertResponseIsSuccessful();
473
+ $this->assertSelectorTextContains('h1', 'Hello World');
443
474
}
444
475
}
445
476
446
- In the above example, you validated that the HTTP response was successful. The
447
- next step is to validate that the page actually contains the expected content.
448
- The ``createClient() `` method returns a client, which is like a browser that
449
- you'll use to crawl your site::
477
+ In the above example, the test validates that the HTTP response was successful
478
+ and the request body contains a `` <h1> `` tag with `` "Hello world" ``. The
479
+ ``createClient() `` method also returns a client, which is like a browser
480
+ that you can use to crawl your site::
450
481
451
482
$crawler = $client->request('GET', '/post/hello-world');
452
483
484
+ // for instance, count the number of ``.comment`` elements on the page
485
+ $this->assertCount(4, $crawler->filter('.comment'));
486
+
453
487
The ``request() `` method (read
454
488
:ref: `more about the request method <testing-request-method-sidebar >`)
455
489
returns a :class: `Symfony\\ Component\\ DomCrawler\\ Crawler ` object which can
456
490
be used to select elements in the response, click on links and submit forms.
457
491
458
- Useful Assertions
459
- ~~~~~~~~~~~~~~~~~
460
-
461
- To get you started faster, here is a list of the most common and
462
- useful test assertions::
463
-
464
- use Symfony\Component\HttpFoundation\Response;
465
-
466
- // ...
467
-
468
- // asserts that there is at least one h2 tag with the class "subtitle"
469
- // the third argument is an optional message shown on failed tests
470
- $this->assertGreaterThan(0, $crawler->filter('h2.subtitle')->count(),
471
- 'There is at least one subtitle'
472
- );
473
-
474
- // asserts that there are exactly 4 h2 tags on the page
475
- $this->assertCount(4, $crawler->filter('h2'));
476
-
477
- // asserts that the "Content-Type" header is "application/json"
478
- $this->assertResponseHeaderSame('Content-Type', 'application/json');
479
- // equivalent to:
480
- $this->assertTrue($client->getResponse()->headers->contains(
481
- 'Content-Type', 'application/json'
482
- ));
483
-
484
- // asserts that the response content contains a string
485
- $this->assertContains('foo', $client->getResponse()->getContent());
486
- // ...or matches a regex
487
- $this->assertRegExp('/foo(bar)?/', $client->getResponse()->getContent());
488
-
489
- // asserts that the response status code is 2xx
490
- $this->assertResponseIsSuccessful();
491
- // equivalent to:
492
- $this->assertTrue($client->getResponse()->isSuccessful());
493
-
494
- // asserts that the response status code is 404 Not Found
495
- $this->assertTrue($client->getResponse()->isNotFound());
496
-
497
- // asserts a specific status code
498
- $this->assertResponseStatusCodeSame(201);
499
- // HTTP status numbers are available as constants too:
500
- // e.g. 201 === Symfony\Component\HttpFoundation\Response::HTTP_CREATED
501
- // equivalent to:
502
- $this->assertEquals(201, $client->getResponse()->getStatusCode());
503
-
504
- // asserts that the response is a redirect to /demo/contact
505
- $this->assertResponseRedirects('/demo/contact');
506
- // equivalent to:
507
- $this->assertTrue($client->getResponse()->isRedirect('/demo/contact'));
508
- // ...or check that the response is a redirect to any URL
509
- $this->assertResponseRedirects();
510
-
511
- .. versionadded :: 4.3
512
-
513
- The ``assertResponseHeaderSame() ``, ``assertResponseIsSuccessful() ``,
514
- ``assertResponseStatusCodeSame() ``, ``assertResponseRedirects() `` and other
515
- related methods were introduced in Symfony 4.3.
516
-
517
492
Working with the Test Client
518
493
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
519
494
@@ -527,41 +502,9 @@ returns a ``Crawler`` instance.
527
502
528
503
.. tip ::
529
504
530
- Hardcoding the request URLs is a best practice for application tests. If the
531
- test generates URLs using the Symfony router, it won't detect any change
532
- made to the application URLs which may impact the end users.
533
-
534
- .. _testing-request-method-sidebar :
535
-
536
- .. sidebar :: More about the ``request()`` Method:
537
-
538
- The full signature of the ``request() `` method is::
539
-
540
- request(
541
- $method,
542
- $uri,
543
- array $parameters = [],
544
- array $files = [],
545
- array $server = [],
546
- $content = null,
547
- $changeHistory = true
548
- )
549
-
550
- The ``server `` array is the raw values that you'd expect to normally
551
- find in the PHP `$_SERVER `_ superglobal. For example, to set the
552
- ``Content-Type `` and ``Referer `` HTTP headers, you'd pass the following (mind
553
- the ``HTTP_ `` prefix for non standard headers)::
554
-
555
- $client->request(
556
- 'GET',
557
- '/post/hello-world',
558
- [],
559
- [],
560
- [
561
- 'CONTENT_TYPE' => 'application/json',
562
- 'HTTP_REFERER' => '/foo/bar',
563
- ]
564
- );
505
+ Hardcoding the request URLs is a best practice for application tests.
506
+ If the test generates URLs using the Symfony router, it won't detect
507
+ any change made to the application URLs which may impact the end users.
565
508
566
509
Use the crawler to find DOM elements in the response. These elements can then
567
510
be used to click on links and submit forms::
@@ -622,6 +565,38 @@ script::
622
565
623
566
$client->insulate();
624
567
568
+ .. _testing-request-method-sidebar :
569
+
570
+ .. sidebar :: More about the ``request()`` Method:
571
+
572
+ The full signature of the ``request() `` method is::
573
+
574
+ request(
575
+ $method,
576
+ $uri,
577
+ array $parameters = [],
578
+ array $files = [],
579
+ array $server = [],
580
+ $content = null,
581
+ $changeHistory = true
582
+ )
583
+
584
+ The ``server `` array is the raw values that you'd expect to normally
585
+ find in the PHP `$_SERVER `_ superglobal. For example, to set the
586
+ ``Content-Type `` and ``Referer `` HTTP headers, you'd pass the following (mind
587
+ the ``HTTP_ `` prefix for non standard headers)::
588
+
589
+ $client->request(
590
+ 'GET',
591
+ '/post/hello-world',
592
+ [],
593
+ [],
594
+ [
595
+ 'CONTENT_TYPE' => 'application/json',
596
+ 'HTTP_REFERER' => '/foo/bar',
597
+ ]
598
+ );
599
+
625
600
AJAX Requests
626
601
.............
627
602
@@ -757,6 +732,65 @@ to be reported by PHPUnit::
757
732
758
733
$client->catchExceptions(false);
759
734
735
+ Useful Assertions
736
+ ~~~~~~~~~~~~~~~~~
737
+
738
+ To get you started faster, here is a list of the most common and
739
+ useful test assertions::
740
+
741
+ use Symfony\Component\HttpFoundation\Response;
742
+
743
+ // ...
744
+
745
+ // asserts that there is at least one h2 tag with the class "subtitle"
746
+ // the third argument is an optional message shown on failed tests
747
+ $this->assertGreaterThan(0, $crawler->filter('h2.subtitle')->count(),
748
+ 'There is at least one subtitle'
749
+ );
750
+
751
+ // asserts that there are exactly 4 h2 tags on the page
752
+ $this->assertCount(4, $crawler->filter('h2'));
753
+
754
+ // asserts that the "Content-Type" header is "application/json"
755
+ $this->assertResponseHeaderSame('Content-Type', 'application/json');
756
+ // equivalent to:
757
+ $this->assertTrue($client->getResponse()->headers->contains(
758
+ 'Content-Type', 'application/json'
759
+ ));
760
+
761
+ // asserts that the response content contains a string
762
+ $this->assertContains('foo', $client->getResponse()->getContent());
763
+ // ...or matches a regex
764
+ $this->assertRegExp('/foo(bar)?/', $client->getResponse()->getContent());
765
+
766
+ // asserts that the response status code is 2xx
767
+ $this->assertResponseIsSuccessful();
768
+ // equivalent to:
769
+ $this->assertTrue($client->getResponse()->isSuccessful());
770
+
771
+ // asserts that the response status code is 404 Not Found
772
+ $this->assertTrue($client->getResponse()->isNotFound());
773
+
774
+ // asserts a specific status code
775
+ $this->assertResponseStatusCodeSame(201);
776
+ // HTTP status numbers are available as constants too:
777
+ // e.g. 201 === Symfony\Component\HttpFoundation\Response::HTTP_CREATED
778
+ // equivalent to:
779
+ $this->assertEquals(201, $client->getResponse()->getStatusCode());
780
+
781
+ // asserts that the response is a redirect to /demo/contact
782
+ $this->assertResponseRedirects('/demo/contact');
783
+ // equivalent to:
784
+ $this->assertTrue($client->getResponse()->isRedirect('/demo/contact'));
785
+ // ...or check that the response is a redirect to any URL
786
+ $this->assertResponseRedirects();
787
+
788
+ .. versionadded :: 4.3
789
+
790
+ The ``assertResponseHeaderSame() ``, ``assertResponseIsSuccessful() ``,
791
+ ``assertResponseStatusCodeSame() ``, ``assertResponseRedirects() `` and other
792
+ related methods were introduced in Symfony 4.3.
793
+
760
794
.. index ::
761
795
single: Tests; Crawler
762
796
@@ -988,10 +1022,10 @@ their type::
988
1022
$client->submit($form, [], ['HTTP_ACCEPT_LANGUAGE' => 'es']);
989
1023
$client->submitForm($button, [], 'POST', ['HTTP_ACCEPT_LANGUAGE' => 'es']);
990
1024
991
- End to End Tests (E2E)
992
- ----------------------
993
-
994
1025
.. TODO
1026
+ End to End Tests (E2E)
1027
+ ----------------------
1028
+
995
1029
* panther
996
1030
* testing javascript
997
1031
* UX or form collections as example?
@@ -1042,8 +1076,8 @@ configuration adds tests from a custom ``lib/tests`` directory:
1042
1076
<!-- ... -->
1043
1077
</phpunit >
1044
1078
1045
- To include other directories in the code coverage, also edit the `` <filter> ``
1046
- section:
1079
+ To include other directories in the ` code coverage analysis `_ , also edit the
1080
+ `` <filter> `` section:
1047
1081
1048
1082
.. code-block :: xml
1049
1083
@@ -1078,6 +1112,8 @@ Learn more
1078
1112
.. _`documentation` : https://phpunit.readthedocs.io/
1079
1113
.. _`Writing Tests for PHPUnit` : https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html
1080
1114
.. _`unit test` : https://en.wikipedia.org/wiki/Unit_testing
1115
+ .. _`DAMADoctrineTestBundle` : https://github.com/dmaicher/doctrine-test-bundle
1116
+ .. _`DoctrineFixturesBundle documentation` : https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html
1081
1117
.. _`$_SERVER` : https://www.php.net/manual/en/reserved.variables.server.php
1082
1118
.. _`SymfonyMakerBundle` : https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html
1083
1119
.. _`code coverage analysis` : https://phpunit.readthedocs.io/en/9.1/code-coverage-analysis.html
0 commit comments