Last active
July 24, 2024 09:24
-
-
Save bpolaszek/0eaff72ad22ac9d832e31db904282928 to your computer and use it in GitHub Desktop.
Symfony shortcuts for Pest
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
# tests/bootstrap.php | |
declare(strict_types=1); | |
namespace App\Test; | |
use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Client; | |
use App\Entity\User; | |
use App\Kernel; | |
use App\Tests\ApiClient; | |
use Doctrine\DBAL\DriverManager; | |
use Doctrine\Migrations\MigratorConfiguration; | |
use Doctrine\ORM\EntityManagerInterface; | |
use Doctrine\ORM\EntityRepository; | |
use Doctrine\ORM\Tools\SchemaTool; | |
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTManager; | |
use Symfony\Component\DependencyInjection\ContainerInterface; | |
use Symfony\Component\Dotenv\Dotenv; | |
use Symfony\Component\EventDispatcher\EventDispatcher; | |
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; | |
use Symfony\Component\Security\Core\User\UserInterface; | |
require dirname(__DIR__) . '/vendor/autoload.php'; | |
if (file_exists(dirname(__DIR__) . '/config/bootstrap.php')) { | |
require dirname(__DIR__) . '/config/bootstrap.php'; | |
} elseif (method_exists(Dotenv::class, 'bootEnv')) { | |
(new Dotenv())->bootEnv(dirname(__DIR__) . '/.env'); | |
} | |
function app(): Kernel | |
{ | |
static $kernel; | |
$kernel ??= (function () { | |
$env = $_ENV['APP_ENV'] ?? $_SERVER['APP_ENV'] ?? 'test'; | |
$debug = $_ENV['APP_DEBUG'] ?? $_SERVER['APP_DEBUG'] ?? true; | |
$kernel = new Kernel((string) $env, (bool) $debug); | |
$kernel->boot(); | |
return $kernel; | |
})(); | |
return $kernel; | |
} | |
/** | |
* Shortcut to the test container (all services are public). | |
*/ | |
function container(): ContainerInterface | |
{ | |
$container = app()->getContainer(); | |
return $container->has('test.service_container') ? $container->get('test.service_container') : $container; | |
} | |
function createBrowser(): Client | |
{ | |
return container()->get('test.api_platform.client'); | |
} | |
function api(?Client $client = null): ApiClient | |
{ | |
return new ApiClient($client ?? createBrowser()); | |
} | |
/** | |
* Create database if not exists. | |
*/ | |
function createDatabase(): void | |
{ | |
$doctrine = container()->get('doctrine'); | |
$connection = $doctrine->getConnection($doctrine->getDefaultConnectionName()); | |
$params = $connection->getParams(); | |
$name = $params['path'] ?? $params['dbname']; | |
unset($params['dbname'], $params['path'], $params['url']); | |
$tmpConnection = DriverManager::getConnection($params); | |
$tmpConnection->connect(); | |
if (\in_array($name, $tmpConnection->getSchemaManager()->listDatabases(), true)) { | |
return; | |
} | |
$tmpConnection->getSchemaManager()->createDatabase( | |
$tmpConnection->getDatabasePlatform()->quoteSingleIdentifier($name) | |
); | |
} | |
function runMigrations(): void | |
{ | |
$dependencyFactory = container()->get('doctrine.migrations.dependency_factory'); | |
$dependencyFactory->getMetadataStorage()->ensureInitialized(); | |
$migratorConfiguration = new MigratorConfiguration(); | |
$planCalculator = $dependencyFactory->getMigrationPlanCalculator(); | |
$migrator = $dependencyFactory->getMigrator(); | |
$version = $dependencyFactory->getVersionAliasResolver()->resolveVersionAlias('latest'); | |
$plan = $planCalculator->getPlanUntilVersion($version); | |
$migrator->migrate($plan, $migratorConfiguration); | |
} | |
function dropDatabase(): void | |
{ | |
$doctrine = container()->get('doctrine'); | |
$connection = $doctrine->getConnection($doctrine->getDefaultConnectionName()); | |
$params = $connection->getParams(); | |
$name = $connection->getDatabasePlatform()->quoteSingleIdentifier($params['dbname']); | |
$connection->getSchemaManager()->dropDatabase($name); | |
} | |
function createSchema(): void | |
{ | |
$entityManager = container()->get(EntityManagerInterface::class); | |
$schemaTool = new SchemaTool($entityManager); | |
$classes = $entityManager->getMetadataFactory()->getAllMetadata(); | |
$schemaTool->createSchema($classes); | |
} | |
/** | |
* @return string[] | |
*/ | |
function tables(): array | |
{ | |
$entityManager = container()->get(EntityManagerInterface::class); | |
$notMappedSuperClassNames = \array_filter( | |
$entityManager->getConfiguration()->getMetadataDriverImpl()->getAllClassNames(), | |
fn (string $class): bool => false === $entityManager->getClassMetadata($class)->isMappedSuperclass, | |
); | |
return \array_map( | |
fn (string $class): string => $entityManager->getClassMetadata($class)->getTableName(), | |
$notMappedSuperClassNames | |
); | |
} | |
function dropSchema(): void | |
{ | |
$entityManager = container()->get(EntityManagerInterface::class); | |
$connection = $entityManager->getConnection(); | |
$connection->query('SET FOREIGN_KEY_CHECKS=0;'); | |
foreach (tables() as $table) { | |
$connection->query(\sprintf('DROP TABLE `%s`;', $table)); | |
} | |
$connection->query('SET FOREIGN_KEY_CHECKS=1;'); | |
// Ensure EntityManager doesn't contain entities after clearing DB | |
$entityManager->clear(); | |
} | |
/** | |
* Use this helper at the beginning of a test to truncate all tables. | |
*/ | |
function clearDatabase(): void | |
{ | |
$entityManager = container()->get(EntityManagerInterface::class); | |
$connection = $entityManager->getConnection(); | |
foreach (tables() as $table) { | |
$connection->query(\sprintf('DELETE FROM `%s`;', $table)); | |
} | |
// Ensure EntityManager doesn't contain entities after clearing DB | |
$entityManager->clear(); | |
} | |
function save(object ...$entities): void | |
{ | |
$em = container()->get(EntityManagerInterface::class); | |
foreach ($entities as $entity) { | |
$em->persist($entity); | |
$em->flush(); | |
} | |
} | |
function remove(object $entity): void | |
{ | |
$em = container()->get(EntityManagerInterface::class); | |
$em->remove($entity); | |
$em->flush(); | |
} | |
function repository(string $className): EntityRepository | |
{ | |
return container()->get(EntityManagerInterface::class)->getRepository($className); | |
} | |
function jwt(UserInterface $user): string | |
{ | |
$jwtManager = new JWTManager( | |
container()->get('lexik_jwt_authentication.encoder'), | |
new EventDispatcher(), // Do not dispatch creation event to avoid dependencies on Request objects | |
); | |
return $jwtManager->create($user); | |
} | |
function login(?UserInterface $user, ?string $providerKey = 'main'): void | |
{ | |
$token = null !== $user ? new UsernamePasswordToken($user, null, $providerKey, $user->getRoles()) : null; | |
container()->get('security.token_storage')->setToken($token); | |
} | |
function logout(): void | |
{ | |
container()->get('security.token_storage')->setToken(null); | |
} | |
function createUser(string $username = 'Bob', array $roles = []): User | |
{ | |
$user = new User(); | |
$user->setUsername($username); | |
$user->setEmail(\sprintf('%[email protected]', \strtr(\strtolower($username), [' ' => '-']))); | |
$user->setRoles($roles); | |
return $user; | |
} | |
function now(): \DateTimeImmutable | |
{ | |
return new \DateTimeImmutable(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Symfony shortcuts for Pest
Using
uses(WebTestCase::class);
is great, but you'll lose autocompletion. So here's thetests/bootstrap.php
I use to make functionnal tests in Pest (Symfony, API-Platform). Customize it to your needs.Example Usages:
Container
Database schema
Entities