Skip to content

Commit cb79b7e

Browse files
committed
feature #30813 New PHPUnit assertions for the WebTestCase (Pierstoval, fabpot)
This PR was merged into the 4.3-dev branch. Discussion ---------- New PHPUnit assertions for the WebTestCase | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | replaces #29990 | License | MIT | Doc PR | n/a While reviewing #29990, and working on some tests, I realized that we could do better by adding PHPUnit constraint classes in various components that are then used in WebTextCase. **Before** ```php <?php namespace App\Tests; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class DefaultControllerTest extends WebTestCase { public function testSomething() { $client = static::createClient(); $crawler = $client->request('GET', '/test'); $this->assertSame(200, $client->getResponse()->getStatusCode()); $this->assertContains('Hello World', $crawler->filter('h1')->text()); } } ``` **After** ```php <?php namespace App\Tests; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class DefaultControllerTest extends WebTestCase { public function testSomething() { $client = static::createClient(); $client->request('GET', '/test'); $this->assertResponseIsSuccessful(); $this->assertSelectorTextContains('h1', 'Hello World'); } } ``` Commits ------- 4f91020c8d added PHPUnit assertions in various components 2f8040ee84 Create new PHPUnit assertions for the WebTestCase
2 parents 95187e4 + 58e572e commit cb79b7e

File tree

7 files changed

+568
-8
lines changed

7 files changed

+568
-8
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
4.3.0
55
-----
66

7+
* added `WebTestAssertions` trait (included by default in `WebTestCase`)
78
* renamed `Client` to `KernelBrowser`
89
* Not passing the project directory to the constructor of the `AssetsInstallCommand` is deprecated. This argument will
910
be mandatory in 5.0.

Test/KernelTestCase.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
*/
2424
abstract class KernelTestCase extends TestCase
2525
{
26-
use KernelShutdownOnTearDownTrait;
26+
use TestCaseSetUpTearDownTrait;
2727

2828
protected static $class;
2929

@@ -37,6 +37,11 @@ abstract class KernelTestCase extends TestCase
3737
*/
3838
protected static $container;
3939

40+
protected function doTearDown(): void
41+
{
42+
static::ensureKernelShutdown();
43+
}
44+
4045
/**
4146
* @return string The Kernel class name
4247
*

Test/KernelShutdownOnTearDownTrait.php renamed to Test/TestCaseSetUpTearDownTrait.php

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,60 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515

16-
// Auto-adapt to PHPUnit 8 that added a `void` return-type to the tearDown method
16+
// Auto-adapt to PHPUnit 8 that added a `void` return-type to the setUp/tearDown methods
1717

1818
if ((new \ReflectionMethod(TestCase::class, 'tearDown'))->hasReturnType()) {
1919
/**
2020
* @internal
2121
*/
22-
trait KernelShutdownOnTearDownTrait
22+
trait TestCaseSetUpTearDownTrait
2323
{
24+
private function doSetUp(): void
25+
{
26+
}
27+
28+
private function doTearDown(): void
29+
{
30+
}
31+
32+
protected function setUp(): void
33+
{
34+
$this->doSetUp();
35+
}
36+
2437
protected function tearDown(): void
2538
{
26-
static::ensureKernelShutdown();
39+
$this->doTearDown();
2740
}
2841
}
2942
} else {
3043
/**
3144
* @internal
3245
*/
33-
trait KernelShutdownOnTearDownTrait
46+
trait TestCaseSetUpTearDownTrait
3447
{
48+
private function doSetUp(): void
49+
{
50+
}
51+
52+
private function doTearDown(): void
53+
{
54+
}
55+
56+
/**
57+
* @return void
58+
*/
59+
protected function setUp()
60+
{
61+
$this->doSetUp();
62+
}
63+
3564
/**
3665
* @return void
3766
*/
3867
protected function tearDown()
3968
{
40-
static::ensureKernelShutdown();
69+
$this->doTearDown();
4170
}
4271
}
4372
}

Test/WebTestAssertions.php

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\Test;
13+
14+
use PHPUnit\Framework\Constraint\LogicalAnd;
15+
use PHPUnit\Framework\Constraint\LogicalNot;
16+
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
17+
use Symfony\Component\BrowserKit\Test\Constraint as BrowserKitConstraint;
18+
use Symfony\Component\DomCrawler\Crawler;
19+
use Symfony\Component\DomCrawler\Test\Constraint as DomCrawlerConstraint;
20+
use Symfony\Component\HttpFoundation\Request;
21+
use Symfony\Component\HttpFoundation\Response;
22+
use Symfony\Component\HttpFoundation\Test\Constraint as ResponseConstraint;
23+
24+
/**
25+
* Ideas borrowed from Laravel Dusk's assertions.
26+
*
27+
* @see https://laravel.com/docs/5.7/dusk#available-assertions
28+
*/
29+
trait WebTestAssertions
30+
{
31+
public static function assertResponseIsSuccessful(string $message = ''): void
32+
{
33+
self::assertThat(static::getResponse(), new ResponseConstraint\ResponseIsSuccessful(), $message);
34+
}
35+
36+
public static function assertResponseStatusCodeSame(int $expectedCode, string $message = ''): void
37+
{
38+
self::assertThat(static::getResponse(), new ResponseConstraint\ResponseStatusCodeSame($expectedCode), $message);
39+
}
40+
41+
public static function assertResponseRedirects(string $expectedLocation = null, int $expectedCode = null, string $message = ''): void
42+
{
43+
$constraint = new ResponseConstraint\ResponseIsRedirected();
44+
if ($expectedLocation) {
45+
$constraint = LogicalAnd::fromConstraints($constraint, new ResponseConstraint\ResponseHeaderSame('Location', $expectedLocation));
46+
}
47+
if ($expectedCode) {
48+
$constraint = LogicalAnd::fromConstraints($constraint, new ResponseConstraint\ResponseStatusCodeSame($expectedCode));
49+
}
50+
51+
self::assertThat(static::getResponse(), $constraint, $message);
52+
}
53+
54+
public static function assertResponseHasHeader(string $headerName, string $message = ''): void
55+
{
56+
self::assertThat(static::getResponse(), new ResponseConstraint\ResponseHasHeader($headerName), $message);
57+
}
58+
59+
public static function assertResponseNotHasHeader(string $headerName, string $message = ''): void
60+
{
61+
self::assertThat(static::getResponse(), new LogicalNot(new ResponseConstraint\ResponseHasHeader($headerName)), $message);
62+
}
63+
64+
public static function assertResponseHeaderSame(string $headerName, string $expectedValue, string $message = ''): void
65+
{
66+
self::assertThat(static::getResponse(), new ResponseConstraint\ResponseHeaderSame($headerName, $expectedValue), $message);
67+
}
68+
69+
public static function assertResponseHeaderNotSame(string $headerName, string $expectedValue, string $message = ''): void
70+
{
71+
self::assertThat(static::getResponse(), new LogicalNot(new ResponseConstraint\ResponseHeaderSame($headerName, $expectedValue)), $message);
72+
}
73+
74+
public static function assertResponseHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void
75+
{
76+
self::assertThat(static::getResponse(), new ResponseConstraint\ResponseHasCookie($name, $path, $domain), $message);
77+
}
78+
79+
public static function assertResponseNotHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void
80+
{
81+
self::assertThat(static::getResponse(), new LogicalNot(new ResponseConstraint\ResponseHasCookie($name, $path, $domain)), $message);
82+
}
83+
84+
public static function assertResponseCookieValueSame(string $name, string $expectedValue, string $path = '/', string $domain = null, string $message = ''): void
85+
{
86+
self::assertThat(static::getResponse(), LogicalAnd::fromConstraints(
87+
new ResponseConstraint\ResponseHasCookie($name, $path, $domain),
88+
new ResponseConstraint\ResponseCookieValueSame($name, $expectedValue, $path, $domain)
89+
), $message);
90+
}
91+
92+
public static function assertBrowserHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void
93+
{
94+
self::assertThat(static::getClient(), new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain), $message);
95+
}
96+
97+
public static function assertBrowserNotHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void
98+
{
99+
self::assertThat(static::getClient(), new LogicalNot(new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain)), $message);
100+
}
101+
102+
public static function assertBrowserCookieValueSame(string $name, string $expectedValue, bool $raw = false, string $path = '/', string $domain = null, string $message = ''): void
103+
{
104+
self::assertThat(static::getClient(), LogicalAnd::fromConstraints(
105+
new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain),
106+
new BrowserKitConstraint\BrowserCookieValueSame($name, $expectedValue, $raw, $path, $domain)
107+
), $message);
108+
}
109+
110+
public static function assertSelectorExists(string $selector, string $message = ''): void
111+
{
112+
self::assertThat(static::getCrawler(), new DomCrawlerConstraint\CrawlerSelectorExists($selector), $message);
113+
}
114+
115+
public static function assertSelectorNotExists(string $selector, string $message = ''): void
116+
{
117+
self::assertThat(static::getCrawler(), new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorExists($selector)), $message);
118+
}
119+
120+
public static function assertSelectorTextContains(string $selector, string $text, string $message = ''): void
121+
{
122+
self::assertThat(static::getCrawler(), LogicalAnd::fromConstraints(
123+
new DomCrawlerConstraint\CrawlerSelectorExists($selector),
124+
new DomCrawlerConstraint\CrawlerSelectorTextContains($selector, $text)
125+
), $message);
126+
}
127+
128+
public static function assertSelectorTextSame(string $selector, string $text, string $message = ''): void
129+
{
130+
self::assertThat(static::getCrawler(), LogicalAnd::fromConstraints(
131+
new DomCrawlerConstraint\CrawlerSelectorExists($selector),
132+
new DomCrawlerConstraint\CrawlerSelectorTextSame($selector, $text)
133+
), $message);
134+
}
135+
136+
public static function assertSelectorTextNotContains(string $selector, string $text, string $message = ''): void
137+
{
138+
self::assertThat(static::getCrawler(), LogicalAnd::fromConstraints(
139+
new DomCrawlerConstraint\CrawlerSelectorExists($selector),
140+
new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorTextContains($selector, $text))
141+
), $message);
142+
}
143+
144+
public static function assertPageTitleSame(string $expectedTitle, string $message = ''): void
145+
{
146+
self::assertSelectorTextSame('title', $expectedTitle, $message);
147+
}
148+
149+
public static function assertPageTitleContains(string $expectedTitle, string $message = ''): void
150+
{
151+
self::assertSelectorTextContains('title', $expectedTitle, $message);
152+
}
153+
154+
public static function assertInputValueSame(string $fieldName, string $expectedValue, string $message = ''): void
155+
{
156+
self::assertThat(static::getCrawler(), LogicalAnd::fromConstraints(
157+
new DomCrawlerConstraint\CrawlerSelectorExists("input[name=\"$fieldName\"]"),
158+
new DomCrawlerConstraint\CrawlerSelectorAttributeValueSame("input[name=\"$fieldName\"]", 'value', $expectedValue)
159+
), $message);
160+
}
161+
162+
public static function assertInputValueNotSame(string $fieldName, string $expectedValue, string $message = ''): void
163+
{
164+
self::assertThat(static::getCrawler(), LogicalAnd::fromConstraints(
165+
new DomCrawlerConstraint\CrawlerSelectorExists("input[name=\"$fieldName\"]"),
166+
new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorAttributeValueSame("input[name=\"$fieldName\"]", 'value', $expectedValue))
167+
), $message);
168+
}
169+
170+
public static function assertRequestAttributeValueSame(string $name, string $expectedValue, string $message = ''): void
171+
{
172+
self::assertThat(static::getRequest(), new ResponseConstraint\RequestAttributeValueSame($name, $expectedValue), $message);
173+
}
174+
175+
public static function assertRouteSame($expectedRoute, array $parameters = [], string $message = ''): void
176+
{
177+
$constraint = new ResponseConstraint\RequestAttributeValueSame('_route', $expectedRoute);
178+
$constraints = [];
179+
foreach ($parameters as $key => $value) {
180+
$constraints[] = new ResponseConstraint\RequestAttributeValueSame($key, $value);
181+
}
182+
if ($constraints) {
183+
$constraint = LogicalAnd::fromConstraints($constraint, ...$constraints);
184+
}
185+
186+
self::assertThat(static::getRequest(), $constraint, $message);
187+
}
188+
189+
private static function getClient(): KernelBrowser
190+
{
191+
if (!static::$client instanceof KernelBrowser) {
192+
static::fail(\sprintf('A client must be set to make assertions on it. Did you forget to call "%s::createClient"?', __CLASS__));
193+
}
194+
195+
return static::$client;
196+
}
197+
198+
private static function getCrawler(): Crawler
199+
{
200+
if (!$crawler = static::getClient()->getCrawler()) {
201+
static::fail('A client must have a crawler to make assertions. Did you forget to make an HTTP request?');
202+
}
203+
204+
return $crawler;
205+
}
206+
207+
private static function getResponse(): Response
208+
{
209+
if (!$response = static::getClient()->getResponse()) {
210+
static::fail('A client must have an HTTP Response to make assertions. Did you forget to make an HTTP request?');
211+
}
212+
213+
return $response;
214+
}
215+
216+
private static function getRequest(): Request
217+
{
218+
if (!$request = static::getClient()->getRequest()) {
219+
static::fail('A client must have an HTTP Request to make assertions. Did you forget to make an HTTP request?');
220+
}
221+
222+
return $request;
223+
}
224+
}

Test/WebTestCase.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@
2121
*/
2222
abstract class WebTestCase extends KernelTestCase
2323
{
24+
use WebTestAssertions;
25+
26+
/** @var Client|null */
27+
protected static $client;
28+
29+
protected function doTearDown(): void
30+
{
31+
parent::doTearDown();
32+
33+
static::$client = null;
34+
}
35+
2436
/**
2537
* Creates a KernelBrowser.
2638
*
@@ -44,6 +56,6 @@ protected static function createClient(array $options = [], array $server = [])
4456

4557
$client->setServerParameters($server);
4658

47-
return $client;
59+
return static::$client = $client;
4860
}
4961
}

0 commit comments

Comments
 (0)