Skip to content

Commit bcb9191

Browse files
authored
Fix inconsistencies across assertion traits (#234)
1 parent bed4de2 commit bcb9191

12 files changed

Lines changed: 123 additions & 86 deletions

src/Codeception/Lib/Connector/Symfony.php

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,7 @@ protected function doRequest(object $request): Response
5353
*/
5454
public function rebootKernel(): void
5555
{
56-
foreach ($this->persistentServices as $name => $_) {
57-
if ($this->container->has($name)) {
58-
$this->persistentServices[$name] = $this->container->get($name);
59-
}
60-
}
56+
$this->updatePersistentServices();
6157

6258
$this->persistDoctrineConnections();
6359

@@ -68,15 +64,7 @@ public function rebootKernel(): void
6864

6965
$this->container = $this->resolveContainer();
7066

71-
foreach ($this->persistentServices as $name => $service) {
72-
try {
73-
$this->container->set($name, $service);
74-
} catch (InvalidArgumentException $e) {
75-
if (function_exists('codecept_debug')) {
76-
codecept_debug("[Symfony] Can't set persistent service {$name}: {$e->getMessage()}");
77-
}
78-
}
79-
}
67+
$this->injectPersistentServices();
8068

8169
$this->getProfiler()?->enable();
8270
}
@@ -116,4 +104,26 @@ private function persistDoctrineConnections(): void
116104
}
117105
})->call($this->kernel->getContainer());
118106
}
107+
108+
private function updatePersistentServices(): void
109+
{
110+
foreach ($this->persistentServices as $name => $_) {
111+
if ($this->container->has($name)) {
112+
$this->persistentServices[$name] = $this->container->get($name);
113+
}
114+
}
115+
}
116+
117+
private function injectPersistentServices(): void
118+
{
119+
foreach ($this->persistentServices as $name => $service) {
120+
try {
121+
$this->container->set($name, $service);
122+
} catch (InvalidArgumentException $e) {
123+
if (function_exists('codecept_debug')) {
124+
codecept_debug("[Symfony] Can't set persistent service {$name}: {$e->getMessage()}");
125+
}
126+
}
127+
}
128+
}
119129
}

src/Codeception/Module/Symfony.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
use Symfony\Bundle\SecurityBundle\DataCollector\SecurityDataCollector;
4242
use Symfony\Component\BrowserKit\AbstractBrowser;
4343
use Symfony\Component\Dotenv\Dotenv;
44-
use Symfony\Component\Finder\Finder;
4544
use Symfony\Component\HttpKernel\DataCollector\TimeDataCollector;
4645
use Symfony\Component\HttpKernel\Kernel;
4746
use Symfony\Component\HttpKernel\Profiler\Profile;
@@ -57,6 +56,7 @@
5756
use function count;
5857
use function extension_loaded;
5958
use function file_exists;
59+
use function glob;
6060
use function implode;
6161
use function ini_get;
6262
use function ini_set;
@@ -259,6 +259,7 @@ public function _after(TestInterface $test): void
259259

260260
$this->cachedResponse = null;
261261
$this->cachedProfile = null;
262+
$this->cachedRoutes = null;
262263

263264
parent::_after($test);
264265
}
@@ -333,8 +334,8 @@ protected function getKernelClass(): string
333334
if (file_exists($expectedKernelPath)) {
334335
include_once $expectedKernelPath;
335336
} else {
336-
foreach ((new Finder())->name('*Kernel.php')->depth('0')->in($path) as $file) {
337-
include_once $file->getRealPath();
337+
foreach (glob($path . DIRECTORY_SEPARATOR . '*Kernel.php') ?: [] as $file) {
338+
include_once $file;
338339
}
339340
}
340341

@@ -450,12 +451,12 @@ private function debugSecurityData(SecurityDataCollector $securityCollector): vo
450451

451452
private function debugMailerData(MessageDataCollector $messageCollector): void
452453
{
453-
$this->debugSection('Emails', sprintf('%d sent', count($messageCollector->getEvents()->getMessages())));
454+
$this->debugSection('Emails', sprintf('%d sent', count($messageCollector->getEvents()->getEvents())));
454455
}
455456

456457
private function debugNotifierData(NotificationDataCollector $notificationCollector): void
457458
{
458-
$this->debugSection('Notifications', sprintf('%d sent', count($notificationCollector->getEvents()->getMessages())));
459+
$this->debugSection('Notifications', sprintf('%d sent', count($notificationCollector->getEvents()->getEvents())));
459460
}
460461

461462
private function debugTimeData(TimeDataCollector $timeCollector): void

src/Codeception/Module/Symfony/BrowserAssertionsTrait.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
use Symfony\Component\HttpFoundation\Test\Constraint\ResponseStatusCodeSame;
2323

2424
use function class_exists;
25-
use function count;
2625
use function sprintf;
2726

2827
trait BrowserAssertionsTrait
@@ -372,7 +371,7 @@ public function submitSymfonyForm(string $name, array $fields): void
372371
}
373372

374373
$node = $this->getClient()->getCrawler()->filter($selector);
375-
$this->assertGreaterThan(0, count($node), sprintf('Form "%s" not found.', $selector));
374+
$this->assertGreaterThan(0, $node->count(), sprintf('Form "%s" not found.', $selector));
376375
$form = $node->form();
377376
$this->getClient()->submit($form, $params);
378377
}

src/Codeception/Module/Symfony/CacheTrait.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ trait CacheTrait
1414
{
1515
private ?object $cachedResponse = null;
1616
private ?Profile $cachedProfile = null;
17+
/** @var array<string, string>|null */
18+
protected ?array $cachedRoutes = null;
1719

1820
/** @var array<string, mixed> */
1921
protected array $state = [];
@@ -53,7 +55,8 @@ protected function getInternalDomains(): array
5355

5456
protected function clearRouterCache(): void
5557
{
56-
unset($this->state['internalDomains'], $this->state['cachedRoutes']);
58+
unset($this->state['internalDomains']);
59+
$this->cachedRoutes = null;
5760
}
5861

5962
/**

src/Codeception/Module/Symfony/DoctrineAssertionsTrait.php

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ trait DoctrineAssertionsTrait
2424
* $I->grabNumRecords(User::class, ['status' => 'active']);
2525
* ```
2626
*
27-
* @param class-string<object> $entityClass Fully-qualified entity class name
27+
* @template T of object
28+
* @param class-string<T> $entityClass Fully-qualified entity class name
2829
* @param array<string, mixed> $criteria Optional query criteria
2930
*/
3031
public function grabNumRecords(string $entityClass, array $criteria = []): int
@@ -44,18 +45,20 @@ public function grabNumRecords(string $entityClass, array $criteria = []): int
4445
* $I->grabRepository(UserRepositoryInterface::class); // interface
4546
* ```
4647
*
47-
* @param object|class-string $mixed
48-
* @return EntityRepository<object>
48+
* @template T of object
49+
* @param object|class-string<T> $entityOrClass
50+
* @return ($entityOrClass is class-string<T> ? EntityRepository<T> : EntityRepository<object>)
4951
*/
50-
public function grabRepository(object|string $mixed): EntityRepository
52+
public function grabRepository(object|string $entityOrClass): EntityRepository
5153
{
52-
$id = is_object($mixed) ? $mixed::class : $mixed;
54+
$id = is_object($entityOrClass) ? $entityOrClass::class : $entityOrClass;
5355

5456
if (interface_exists($id) || is_subclass_of($id, EntityRepository::class)) {
5557
$repo = $this->grabService($id);
5658
if (!($repo instanceof EntityRepository && $repo instanceof $id)) {
5759
Assert::fail(sprintf("'%s' is not an entity repository", $id));
5860
}
61+
/** @var EntityRepository<T>|EntityRepository<object> $repo */
5962
return $repo;
6063
}
6164

@@ -64,6 +67,7 @@ public function grabRepository(object|string $mixed): EntityRepository
6467
Assert::fail(sprintf("'%s' is not a managed Doctrine entity", $id));
6568
}
6669

70+
/** @var EntityRepository<T>|EntityRepository<object> */
6771
return $em->getRepository($id);
6872
}
6973

@@ -77,8 +81,9 @@ public function grabRepository(object|string $mixed): EntityRepository
7781
* $I->seeNumRecords(80, User::class);
7882
* ```
7983
*
84+
* @template T of object
8085
* @param int $expectedNum Expected count
81-
* @param class-string<object> $className Entity class
86+
* @param class-string<T> $className Entity class
8287
* @param array<string, mixed> $criteria Optional criteria
8388
*/
8489
public function seeNumRecords(int $expectedNum, string $className, array $criteria = []): void

src/Codeception/Module/Symfony/DomCrawlerAssertionsTrait.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,10 @@ private function assertCheckboxState(string $fieldName, bool $checked, string $m
170170
$this->assertThatCrawler($constraint, $message);
171171
}
172172

173-
private function assertInputValue(string $fieldName, string $value, bool $same, string $message): void
173+
private function assertInputValue(string $fieldName, string $expectedValue, bool $same, string $message): void
174174
{
175175
$this->assertThatCrawler(new CrawlerSelectorExists("input[name=\"$fieldName\"]"), $message);
176-
$constraint = new CrawlerSelectorAttributeValueSame("input[name=\"$fieldName\"]", 'value', $value);
176+
$constraint = new CrawlerSelectorAttributeValueSame("input[name=\"$fieldName\"]", 'value', $expectedValue);
177177
if (!$same) {
178178
$constraint = new LogicalNot($constraint);
179179
}

src/Codeception/Module/Symfony/FormAssertionsTrait.php

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use Symfony\Component\Form\Extension\DataCollector\FormDataCollector;
99
use Symfony\Component\VarDumper\Cloner\Data;
1010

11-
use function count;
1211
use function implode;
1312
use function is_array;
1413
use function is_int;
@@ -26,18 +25,18 @@ trait FormAssertionsTrait
2625
* $I->assertFormValue('#loginForm', 'username', 'john_doe');
2726
* ```
2827
*/
29-
public function assertFormValue(string $formSelector, string $fieldName, string $value, string $message = ''): void
28+
public function assertFormValue(string $formSelector, string $fieldName, string $expectedValue, string $message = ''): void
3029
{
3130
$node = $this->getClient()->getCrawler()->filter($formSelector);
32-
$this->assertGreaterThan(0, count($node), sprintf('Form "%s" not found.', $formSelector));
31+
$this->assertGreaterThan(0, $node->count(), sprintf('Form "%s" not found.', $formSelector));
3332

3433
$values = $node->form()->getValues();
3534
$this->assertArrayHasKey(
3635
$fieldName,
3736
$values,
3837
$message ?: sprintf('Field "%s" not found in form "%s".', $fieldName, $formSelector)
3938
);
40-
$this->assertSame($value, $values[$fieldName]);
39+
$this->assertSame($expectedValue, $values[$fieldName]);
4140
}
4241

4342
/**
@@ -51,7 +50,7 @@ public function assertFormValue(string $formSelector, string $fieldName, string
5150
public function assertNoFormValue(string $formSelector, string $fieldName, string $message = ''): void
5251
{
5352
$node = $this->getClient()->getCrawler()->filter($formSelector);
54-
$this->assertGreaterThan(0, count($node), sprintf('Form "%s" not found.', $formSelector));
53+
$this->assertGreaterThan(0, $node->count(), sprintf('Form "%s" not found.', $formSelector));
5554

5655
$values = $node->form()->getValues();
5756
$this->assertArrayNotHasKey(
@@ -86,19 +85,11 @@ public function dontSeeFormErrors(): void
8685
*/
8786
public function seeFormErrorMessage(string $field, ?string $message = null): void
8887
{
89-
$errors = $this->getErrorsForField($field);
90-
91-
if ($errors === []) {
92-
Assert::fail("No form error message for field '{$field}'.");
93-
}
88+
$collector = $this->grabFormCollector(__FUNCTION__);
89+
/** @var array<string, mixed> $formsData */
90+
$formsData = $this->getRawCollectorData($collector)['forms'] ?? [];
9491

95-
if ($message !== null) {
96-
$this->assertStringContainsString(
97-
$message,
98-
implode("\n", $errors),
99-
sprintf("There is an error message for the field '%s', but it does not match the expected message.", $field)
100-
);
101-
}
92+
$this->assertFormErrorMessage($field, $message, $formsData);
10293
}
10394

10495
/**
@@ -140,8 +131,34 @@ public function seeFormErrorMessage(string $field, ?string $message = null): voi
140131
*/
141132
public function seeFormErrorMessages(array $expectedErrors): void
142133
{
134+
$collector = $this->grabFormCollector(__FUNCTION__);
135+
/** @var array<string, mixed> $formsData */
136+
$formsData = $this->getRawCollectorData($collector)['forms'] ?? [];
137+
143138
foreach ($expectedErrors as $field => $msg) {
144-
is_int($field) ? $this->seeFormErrorMessage((string) $msg) : $this->seeFormErrorMessage($field, $msg);
139+
if (is_int($field)) {
140+
$this->assertFormErrorMessage((string) $msg, null, $formsData);
141+
} else {
142+
$this->assertFormErrorMessage($field, $msg, $formsData);
143+
}
144+
}
145+
}
146+
147+
/** @param array<string, mixed> $formsData */
148+
private function assertFormErrorMessage(string $field, ?string $message, array $formsData): void
149+
{
150+
$errors = $this->getErrorsForField($field, $formsData);
151+
152+
if ($errors === []) {
153+
Assert::fail("No form error message for field '{$field}'.");
154+
}
155+
156+
if ($message !== null) {
157+
$this->assertStringContainsString(
158+
$message,
159+
implode("\n", $errors),
160+
sprintf("There is an error message for the field '%s', but it does not match the expected message.", $field)
161+
);
145162
}
146163
}
147164

@@ -172,16 +189,11 @@ private function getFormErrorsCount(string $function): int
172189
}
173190

174191
/**
192+
* @param array<string, mixed> $formsData
175193
* @return list<string>
176194
*/
177-
private function getErrorsForField(string $field): array
195+
private function getErrorsForField(string $field, array $formsData): array
178196
{
179-
$collector = $this->grabFormCollector('seeFormErrorMessage');
180-
$formsData = $this->getRawCollectorData($collector)['forms'] ?? [];
181-
if (!is_array($formsData)) {
182-
return [];
183-
}
184-
185197
$errorsForField = [];
186198
$fieldFound = false;
187199

@@ -215,11 +227,11 @@ private function getErrorsForField(string $field): array
215227
/** @return array<string, mixed> */
216228
private function getRawCollectorData(FormDataCollector $collector): array
217229
{
218-
$data = $collector->getData();
219-
if ($data instanceof Data) {
220-
$data = $data->getValue(true);
230+
$collectorData = $collector->getData();
231+
if ($collectorData instanceof Data) {
232+
$collectorData = $collectorData->getValue(true);
221233
}
222234
/** @var array<string, mixed> */
223-
return is_array($data) ? $data : [];
235+
return is_array($collectorData) ? $collectorData : [];
224236
}
225237
}

src/Codeception/Module/Symfony/HttpClientAssertionsTrait.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,13 @@ private function getHttpClientTraces(string $httpClientId, string $function): ar
145145
return $clientData['traces'];
146146
}
147147

148-
private function extractValue(mixed $value): mixed
148+
private function extractValue(mixed $traceData): mixed
149149
{
150150
return match (true) {
151-
$value instanceof Data => $value->getValue(true),
152-
is_object($value) && method_exists($value, 'getValue') => $value->getValue(true),
153-
$value instanceof Stringable => (string) $value,
154-
default => $value,
151+
$traceData instanceof Data => $traceData->getValue(true),
152+
is_object($traceData) && method_exists($traceData, 'getValue') => $traceData->getValue(true),
153+
$traceData instanceof Stringable => (string) $traceData,
154+
default => $traceData,
155155
};
156156
}
157157

src/Codeception/Module/Symfony/RouterAssertionsTrait.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,13 @@ private function getCurrentRouteMatch(string $routeName): array
118118

119119
private function findRouteByActionOrFail(string $action): string
120120
{
121+
if (isset($this->cachedRoutes[$action])) {
122+
return $this->cachedRoutes[$action];
123+
}
124+
121125
foreach ($this->getCachedRoutes() as $ctrl => $name) {
122126
if (str_ends_with($ctrl, $action)) {
123-
return $name;
127+
return $this->cachedRoutes[$action] = $name;
124128
}
125129
}
126130

@@ -130,9 +134,8 @@ private function findRouteByActionOrFail(string $action): string
130134
/** @return array<string, string> */
131135
private function getCachedRoutes(): array
132136
{
133-
if (isset($this->state['cachedRoutes'])) {
134-
/** @var array<string, string> */
135-
return $this->state['cachedRoutes'];
137+
if ($this->cachedRoutes !== null) {
138+
return $this->cachedRoutes;
136139
}
137140

138141
$routes = [];
@@ -143,7 +146,7 @@ private function getCachedRoutes(): array
143146
}
144147
}
145148

146-
return $this->state['cachedRoutes'] = $routes;
149+
return $this->cachedRoutes = $routes;
147150
}
148151

149152
private function assertRouteExists(string $routeName): void

0 commit comments

Comments
 (0)