Skip to content

Commit e96e209

Browse files
committed
Use traits for methods that belong to the public API (#94)
1 parent 776a689 commit e96e209

12 files changed

+1275
-1131
lines changed

src/Codeception/Module/Symfony.php

Lines changed: 25 additions & 1131 deletions
Large diffs are not rendered by default.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codeception\Module\Symfony;
6+
7+
use Codeception\Lib\Connector\Symfony as SymfonyConnector;
8+
use Symfony\Component\HttpFoundation\Response;
9+
use function sprintf;
10+
11+
trait BrowserAssertionsTrait
12+
{
13+
/**
14+
* Reboot client's kernel.
15+
* Can be used to manually reboot kernel when 'rebootable_client' => false
16+
*
17+
* ```php
18+
* <?php
19+
*
20+
* // Perform some requests
21+
*
22+
* $I->rebootClientKernel();
23+
*
24+
* // Perform other requests
25+
*
26+
* ```
27+
*
28+
*/
29+
public function rebootClientKernel(): void
30+
{
31+
if ($this->client instanceof SymfonyConnector) {
32+
$this->client->rebootKernel();
33+
}
34+
}
35+
36+
/**
37+
* Goes to a page and check that it can be accessed.
38+
*
39+
* ```php
40+
* <?php
41+
* $I->seePageIsAvailable('/dashboard');
42+
* ```
43+
*
44+
* @param string $url
45+
*/
46+
public function seePageIsAvailable(string $url): void
47+
{
48+
$this->amOnPage($url);
49+
$this->seeResponseCodeIsSuccessful();
50+
$this->seeInCurrentUrl($url);
51+
}
52+
53+
/**
54+
* Goes to a page and check that it redirects to another.
55+
*
56+
* ```php
57+
* <?php
58+
* $I->seePageRedirectsTo('/admin', '/login');
59+
* ```
60+
*
61+
* @param string $page
62+
* @param string $redirectsTo
63+
*/
64+
public function seePageRedirectsTo(string $page, string $redirectsTo): void
65+
{
66+
$this->client->followRedirects(false);
67+
$this->amOnPage($page);
68+
/** @var Response $response */
69+
$response = $this->client->getResponse();
70+
$this->assertTrue(
71+
$response->isRedirection()
72+
);
73+
$this->client->followRedirect();
74+
$this->seeInCurrentUrl($redirectsTo);
75+
}
76+
77+
/**
78+
* Submit a form specifying the form name only once.
79+
*
80+
* Use this function instead of $I->submitForm() to avoid repeating the form name in the field selectors.
81+
* If you customized the names of the field selectors use $I->submitForm() for full control.
82+
*
83+
* ```php
84+
* <?php
85+
* $I->submitSymfonyForm('login_form', [
86+
* '[email]' => 'john_doe@gmail.com',
87+
* '[password]' => 'secretForest'
88+
* ]);
89+
* ```
90+
*
91+
* @param string $name
92+
* @param string[] $fields
93+
*/
94+
public function submitSymfonyForm(string $name, array $fields): void
95+
{
96+
$selector = sprintf('form[name=%s]', $name);
97+
98+
$params = [];
99+
foreach ($fields as $key => $value) {
100+
$fixedKey = sprintf('%s%s', $name, $key);
101+
$params[$fixedKey] = $value;
102+
}
103+
$button = sprintf('%s_submit', $name);
104+
105+
$this->submitForm($selector, $params, $button);
106+
}
107+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codeception\Module\Symfony;
6+
7+
use Symfony\Bundle\FrameworkBundle\Console\Application;
8+
use Symfony\Component\Console\Tester\CommandTester;
9+
10+
trait ConsoleAssertionsTrait
11+
{
12+
/**
13+
* Run Symfony console command, grab response and return as string.
14+
* Recommended to use for integration or functional testing.
15+
*
16+
* ``` php
17+
* <?php
18+
* $result = $I->runSymfonyConsoleCommand('hello:world', ['arg' => 'argValue', 'opt1' => 'optValue'], ['input']);
19+
* ```
20+
*
21+
* @param string $command The console command to execute
22+
* @param array $parameters Parameters (arguments and options) to pass to the command
23+
* @param array $consoleInputs Console inputs (e.g. used for interactive questions)
24+
* @param int $expectedExitCode The expected exit code of the command
25+
* @return string Returns the console output of the command
26+
*/
27+
public function runSymfonyConsoleCommand(string $command, array $parameters = [], array $consoleInputs = [], int $expectedExitCode = 0): string
28+
{
29+
$kernel = $this->grabService('kernel');
30+
$application = new Application($kernel);
31+
$consoleCommand = $application->find($command);
32+
$commandTester = new CommandTester($consoleCommand);
33+
$commandTester->setInputs($consoleInputs);
34+
35+
$parameters = ['command' => $command] + $parameters;
36+
$exitCode = $commandTester->execute($parameters);
37+
$output = $commandTester->getDisplay();
38+
39+
$this->assertEquals(
40+
$expectedExitCode,
41+
$exitCode,
42+
'Command did not exit with code '.$expectedExitCode
43+
.' but with '.$exitCode.': '.$output
44+
);
45+
46+
return $output;
47+
}
48+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codeception\Module\Symfony;
6+
7+
use function class_exists;
8+
use function get_class;
9+
use function interface_exists;
10+
use function is_object;
11+
use function is_string;
12+
use function is_subclass_of;
13+
use function json_encode;
14+
use function sprintf;
15+
16+
trait DoctrineAssertionsTrait
17+
{
18+
/**
19+
* Retrieves number of records from database
20+
* 'id' is the default search parameter.
21+
*
22+
* ```php
23+
* <?php
24+
* $I->grabNumRecords('User::class', ['name' => 'davert']);
25+
* ```
26+
*
27+
* @param string $entityClass The entity class
28+
* @param array $criteria Optional query criteria
29+
* @return int
30+
*/
31+
public function grabNumRecords(string $entityClass, array $criteria = []): int
32+
{
33+
$em = $this->_getEntityManager();
34+
$repository = $em->getRepository($entityClass);
35+
36+
if (empty($criteria)) {
37+
return (int)$repository->createQueryBuilder('a')
38+
->select('count(a.id)')
39+
->getQuery()
40+
->getSingleScalarResult();
41+
}
42+
return $repository->count($criteria);
43+
}
44+
45+
/**
46+
* Grab a Doctrine entity repository.
47+
* Works with objects, entities, repositories, and repository interfaces.
48+
*
49+
* ```php
50+
* <?php
51+
* $I->grabRepository($user);
52+
* $I->grabRepository(User::class);
53+
* $I->grabRepository(UserRepository::class);
54+
* $I->grabRepository(UserRepositoryInterface::class);
55+
* ```
56+
*
57+
* @param object|string $mixed
58+
* @return \Doctrine\ORM\EntityRepository|null
59+
*/
60+
public function grabRepository($mixed)
61+
{
62+
$entityRepoClass = '\Doctrine\ORM\EntityRepository';
63+
$isNotARepo = function () use ($mixed): void {
64+
$this->fail(
65+
sprintf("'%s' is not an entity repository", $mixed)
66+
);
67+
};
68+
$getRepo = function () use ($mixed, $entityRepoClass, $isNotARepo) {
69+
if (!$repo = $this->grabService($mixed)) return null;
70+
if (!$repo instanceof $entityRepoClass) {
71+
$isNotARepo();
72+
return null;
73+
}
74+
return $repo;
75+
};
76+
77+
if (is_object($mixed)) {
78+
$mixed = get_class($mixed);
79+
}
80+
81+
if (interface_exists($mixed)) {
82+
return $getRepo();
83+
}
84+
85+
if (!is_string($mixed) || !class_exists($mixed) ) {
86+
$isNotARepo();
87+
return null;
88+
}
89+
90+
if (is_subclass_of($mixed, $entityRepoClass)){
91+
return $getRepo();
92+
}
93+
94+
$em = $this->_getEntityManager();
95+
if ($em->getMetadataFactory()->isTransient($mixed)) {
96+
$isNotARepo();
97+
return null;
98+
}
99+
100+
return $em->getRepository($mixed);
101+
}
102+
103+
/**
104+
* Checks that number of given records were found in database.
105+
* 'id' is the default search parameter.
106+
*
107+
* ```php
108+
* <?php
109+
* $I->seeNumRecords(1, User::class, ['name' => 'davert']);
110+
* $I->seeNumRecords(80, User::class);
111+
* ```
112+
*
113+
* @param int $expectedNum Expected number of records
114+
* @param string $className A doctrine entity
115+
* @param array $criteria Optional query criteria
116+
*/
117+
public function seeNumRecords(int $expectedNum, string $className, array $criteria = []): void
118+
{
119+
$currentNum = $this->grabNumRecords($className, $criteria);
120+
121+
$this->assertEquals(
122+
$expectedNum,
123+
$currentNum,
124+
sprintf(
125+
'The number of found %s (%d) does not match expected number %d with %s',
126+
$className, $currentNum, $expectedNum, json_encode($criteria)
127+
)
128+
);
129+
}
130+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codeception\Module\Symfony;
6+
7+
use Symfony\Component\HttpKernel\DataCollector\EventDataCollector;
8+
use Symfony\Component\VarDumper\Cloner\Data;
9+
use function get_class;
10+
use function is_array;
11+
use function is_object;
12+
use function strpos;
13+
14+
trait EventsAssertionsTrait
15+
{
16+
/**
17+
* Make sure events did not fire during the test.
18+
*
19+
* ``` php
20+
* <?php
21+
* $I->dontSeeEventTriggered('App\MyEvent');
22+
* $I->dontSeeEventTriggered(new App\Events\MyEvent());
23+
* $I->dontSeeEventTriggered(['App\MyEvent', 'App\MyOtherEvent']);
24+
* ```
25+
*
26+
* @param string|object|string[] $expected
27+
*/
28+
public function dontSeeEventTriggered($expected): void
29+
{
30+
/** @var EventDataCollector $eventCollector */
31+
$eventCollector = $this->grabCollector('events', __FUNCTION__);
32+
33+
/** @var Data $data */
34+
$data = $eventCollector->getNotCalledListeners();
35+
36+
$actual = $data->getValue(true);
37+
$expected = is_array($expected) ? $expected : [$expected];
38+
39+
foreach ($expected as $expectedEvent) {
40+
$notTriggered = false;
41+
$expectedEvent = is_object($expectedEvent) ? get_class($expectedEvent) : $expectedEvent;
42+
43+
foreach ($actual as $actualEvent) {
44+
if (strpos($actualEvent['pretty'], $expectedEvent) === 0) {
45+
$notTriggered = true;
46+
}
47+
}
48+
$this->assertTrue($notTriggered, "The '$expectedEvent' event triggered");
49+
}
50+
}
51+
52+
/**
53+
* Make sure events fired during the test.
54+
*
55+
* ``` php
56+
* <?php
57+
* $I->seeEventTriggered('App\MyEvent');
58+
* $I->seeEventTriggered(new App\Events\MyEvent());
59+
* $I->seeEventTriggered(['App\MyEvent', 'App\MyOtherEvent']);
60+
* ```
61+
*
62+
* @param string|object|string[] $expected
63+
*/
64+
public function seeEventTriggered($expected): void
65+
{
66+
/** @var EventDataCollector $eventCollector */
67+
$eventCollector = $this->grabCollector('events', __FUNCTION__);
68+
69+
/** @var Data $data */
70+
$data = $eventCollector->getCalledListeners();
71+
72+
if ($data->count() === 0) {
73+
$this->fail('No event was triggered');
74+
}
75+
76+
$actual = $data->getValue(true);
77+
$expected = is_array($expected) ? $expected : [$expected];
78+
79+
foreach ($expected as $expectedEvent) {
80+
$triggered = false;
81+
$expectedEvent = is_object($expectedEvent) ? get_class($expectedEvent) : $expectedEvent;
82+
83+
foreach ($actual as $actualEvent) {
84+
if (strpos($actualEvent['pretty'], $expectedEvent) === 0) {
85+
$triggered = true;
86+
}
87+
}
88+
$this->assertTrue($triggered, "The '$expectedEvent' event did not trigger");
89+
}
90+
}
91+
}

0 commit comments

Comments
 (0)