@@ -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
@@ -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
-----------------
@@ -198,7 +201,15 @@ method::
198
201
.. tip ::
199
202
200
203
It is recommended to run your test with ``debug `` set to ``false `` on
201
- your CI server, as it significantly improves test performance.
204
+ your CI server, as it significantly improves test performance. This
205
+ disables clearing the cache. If your tests don't run in a clean
206
+ environment each time, you have to manually clear it using for instance
207
+ this code in ``tests/bootstrap.php ``::
208
+
209
+ // ...
210
+
211
+ // ensure a fresh cache when debug mode is disabled
212
+ (new \Symfony\Component\Filesystem\Filesystem())->remove(__DIR__.'/../var/cache/test');
202
213
203
214
Customizing Environment Variables
204
215
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -243,7 +254,7 @@ the container is stored in ``self::$container``::
243
254
{
244
255
public function testSomething()
245
256
{
246
- // boot the Symfony kernel
257
+ // (1) boot the Symfony kernel
247
258
self::bootKernel();
248
259
249
260
// (2) use self::$container to access the service container
@@ -267,6 +278,8 @@ It gives you access to both the public services and the non-removed
267
278
are not used by any other services), you need to declare those private
268
279
services as public in the ``config/services_test.yaml `` file.
269
280
281
+ .. _testing-databases :
282
+
270
283
Configuring a Database for Tests
271
284
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
272
285
@@ -410,21 +423,35 @@ have a very specific workflow:
410
423
#. Test the response;
411
424
#. Rinse and repeat.
412
425
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
426
+ .. note ::
418
427
419
- $ composer require --dev symfony/test-pack
428
+ The tools used in this section can be installed via the ``symfony/test-pack ``,
429
+ use ``composer require symfony/test-pack `` if you haven't done so already.
420
430
421
431
Write Your First Application Test
422
432
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
423
433
424
434
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::
435
+ directory of your application. They often extend
436
+ :class: `Symfony\\ Bundle\\ FrameworkBundle\\ Test\\ WebTestCase `. This class
437
+ adds special logic on top of the ``KernelTestCase ``. You can read more
438
+ about that in the above :ref: `section on integration tests <integration-tests >`.
439
+
440
+ If you want to test the pages handled by your
441
+ ``PostController `` class, start by creating a new ``PostControllerTest ``
442
+ using the ``make:test `` command of the `SymfonyMakerBundle `_:
443
+
444
+ .. code-block :: terminal
445
+
446
+ $ php bin/console make:test
447
+
448
+ Which test type would you like?:
449
+ > WebTestCase
450
+
451
+ The name of the test class (e.g. BlogPostTest):
452
+ > Controller\PostControllerTest
453
+
454
+ This creates the following test class::
428
455
429
456
// tests/Controller/PostControllerTest.php
430
457
namespace App\Tests\Controller;
@@ -433,87 +460,36 @@ file that extends a special ``WebTestCase`` class::
433
460
434
461
class PostControllerTest extends WebTestCase
435
462
{
436
- public function testShowPost()
463
+ public function testSomething(): void
437
464
{
465
+ // This calls KernelTestCase::bootKernel(), and creates a
466
+ // "client" that is acting as the browser
438
467
$client = static::createClient();
439
468
440
- $client->request('GET', '/post/hello-world');
469
+ // Request a specific page
470
+ $crawler = $client->request('GET', '/');
441
471
442
- $this->assertEquals(200, $client->getResponse()->getStatusCode());
472
+ // Validate a successful response and some content
473
+ $this->assertResponseIsSuccessful();
474
+ $this->assertSelectorTextContains('h1', 'Hello World');
443
475
}
444
476
}
445
477
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::
478
+ In the above example, the test validates that the HTTP response was successful
479
+ and the request body contains a `` <h1> `` tag with `` "Hello world" ``. The
480
+ ``createClient() `` method also returns a client, which is like a browser
481
+ that you can use to crawl your site::
450
482
451
483
$crawler = $client->request('GET', '/post/hello-world');
452
484
485
+ // for instance, count the number of ``.comment`` elements on the page
486
+ $this->assertCount(4, $crawler->filter('.comment'));
487
+
453
488
The ``request() `` method (read
454
489
:ref: `more about the request method <testing-request-method-sidebar >`)
455
490
returns a :class: `Symfony\\ Component\\ DomCrawler\\ Crawler ` object which can
456
491
be used to select elements in the response, click on links and submit forms.
457
492
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
493
Working with the Test Client
518
494
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
519
495
@@ -527,41 +503,9 @@ returns a ``Crawler`` instance.
527
503
528
504
.. tip ::
529
505
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
- );
506
+ Hardcoding the request URLs is a best practice for application tests.
507
+ If the test generates URLs using the Symfony router, it won't detect
508
+ any change made to the application URLs which may impact the end users.
565
509
566
510
Use the crawler to find DOM elements in the response. These elements can then
567
511
be used to click on links and submit forms::
@@ -622,6 +566,38 @@ script::
622
566
623
567
$client->insulate();
624
568
569
+ .. _testing-request-method-sidebar :
570
+
571
+ .. sidebar :: More about the ``request()`` Method:
572
+
573
+ The full signature of the ``request() `` method is::
574
+
575
+ request(
576
+ $method,
577
+ $uri,
578
+ array $parameters = [],
579
+ array $files = [],
580
+ array $server = [],
581
+ $content = null,
582
+ $changeHistory = true
583
+ )
584
+
585
+ The ``server `` array is the raw values that you'd expect to normally
586
+ find in the PHP `$_SERVER `_ superglobal. For example, to set the
587
+ ``Content-Type `` and ``Referer `` HTTP headers, you'd pass the following (mind
588
+ the ``HTTP_ `` prefix for non standard headers)::
589
+
590
+ $client->request(
591
+ 'GET',
592
+ '/post/hello-world',
593
+ [],
594
+ [],
595
+ [
596
+ 'CONTENT_TYPE' => 'application/json',
597
+ 'HTTP_REFERER' => '/foo/bar',
598
+ ]
599
+ );
600
+
625
601
AJAX Requests
626
602
.............
627
603
@@ -757,6 +733,65 @@ to be reported by PHPUnit::
757
733
758
734
$client->catchExceptions(false);
759
735
736
+ Useful Assertions
737
+ ~~~~~~~~~~~~~~~~~
738
+
739
+ To get you started faster, here is a list of the most common and
740
+ useful test assertions::
741
+
742
+ use Symfony\Component\HttpFoundation\Response;
743
+
744
+ // ...
745
+
746
+ // asserts that there is at least one h2 tag with the class "subtitle"
747
+ // the third argument is an optional message shown on failed tests
748
+ $this->assertGreaterThan(0, $crawler->filter('h2.subtitle')->count(),
749
+ 'There is at least one subtitle'
750
+ );
751
+
752
+ // asserts that there are exactly 4 h2 tags on the page
753
+ $this->assertCount(4, $crawler->filter('h2'));
754
+
755
+ // asserts that the "Content-Type" header is "application/json"
756
+ $this->assertResponseHeaderSame('Content-Type', 'application/json');
757
+ // equivalent to:
758
+ $this->assertTrue($client->getResponse()->headers->contains(
759
+ 'Content-Type', 'application/json'
760
+ ));
761
+
762
+ // asserts that the response content contains a string
763
+ $this->assertContains('foo', $client->getResponse()->getContent());
764
+ // ...or matches a regex
765
+ $this->assertRegExp('/foo(bar)?/', $client->getResponse()->getContent());
766
+
767
+ // asserts that the response status code is 2xx
768
+ $this->assertResponseIsSuccessful();
769
+ // equivalent to:
770
+ $this->assertTrue($client->getResponse()->isSuccessful());
771
+
772
+ // asserts that the response status code is 404 Not Found
773
+ $this->assertTrue($client->getResponse()->isNotFound());
774
+
775
+ // asserts a specific status code
776
+ $this->assertResponseStatusCodeSame(201);
777
+ // HTTP status numbers are available as constants too:
778
+ // e.g. 201 === Symfony\Component\HttpFoundation\Response::HTTP_CREATED
779
+ // equivalent to:
780
+ $this->assertEquals(201, $client->getResponse()->getStatusCode());
781
+
782
+ // asserts that the response is a redirect to /demo/contact
783
+ $this->assertResponseRedirects('/demo/contact');
784
+ // equivalent to:
785
+ $this->assertTrue($client->getResponse()->isRedirect('/demo/contact'));
786
+ // ...or check that the response is a redirect to any URL
787
+ $this->assertResponseRedirects();
788
+
789
+ .. versionadded :: 4.3
790
+
791
+ The ``assertResponseHeaderSame() ``, ``assertResponseIsSuccessful() ``,
792
+ ``assertResponseStatusCodeSame() ``, ``assertResponseRedirects() `` and other
793
+ related methods were introduced in Symfony 4.3.
794
+
760
795
.. index ::
761
796
single: Tests; Crawler
762
797
@@ -988,10 +1023,10 @@ their type::
988
1023
$client->submit($form, [], ['HTTP_ACCEPT_LANGUAGE' => 'es']);
989
1024
$client->submitForm($button, [], 'POST', ['HTTP_ACCEPT_LANGUAGE' => 'es']);
990
1025
991
- End to End Tests (E2E)
992
- ----------------------
993
-
994
1026
.. TODO
1027
+ End to End Tests (E2E)
1028
+ ----------------------
1029
+
995
1030
* panther
996
1031
* testing javascript
997
1032
* UX or form collections as example?
@@ -1078,6 +1113,8 @@ Learn more
1078
1113
.. _`documentation` : https://phpunit.readthedocs.io/
1079
1114
.. _`Writing Tests for PHPUnit` : https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html
1080
1115
.. _`unit test` : https://en.wikipedia.org/wiki/Unit_testing
1116
+ .. _`DAMADoctrineTestBundle` : https://github.com/dmaicher/doctrine-test-bundle
1117
+ .. _`DoctrineFixturesBundle documentation` : https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html
1081
1118
.. _`$_SERVER` : https://www.php.net/manual/en/reserved.variables.server.php
1082
1119
.. _`SymfonyMakerBundle` : https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html
1083
1120
.. _`code coverage analysis` : https://phpunit.readthedocs.io/en/9.1/code-coverage-analysis.html
0 commit comments