Skip to content

Instantly share code, notes, and snippets.

@bpolaszek
Last active December 19, 2023 19:49
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bpolaszek/0eaff72ad22ac9d832e31db904282928 to your computer and use it in GitHub Desktop.
Save bpolaszek/0eaff72ad22ac9d832e31db904282928 to your computer and use it in GitHub Desktop.
Symfony shortcuts for Pest
<?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('%s@example.com', \strtr(\strtolower($username), [' ' => '-'])));
$user->setRoles($roles);
return $user;
}
function now(): \DateTimeImmutable
{
return new \DateTimeImmutable();
}
@bpolaszek
Copy link
Author

bpolaszek commented Jun 26, 2020

Symfony shortcuts for Pest

Using uses(WebTestCase::class); is great, but you'll lose autocompletion. So here's the tests/bootstrap.php I use to make functionnal tests in Pest (Symfony, API-Platform). Customize it to your needs.

Example Usages:

Container

namespace App\Test;

 // Boots kernel if not already done, retrieve service even if private (if 'test' === getenv(APP_ENV))
container()->get('some.service');

Database schema

namespace App\Test;

beforeAll(function() {
    createDatabase();
    runMigrations();
});
afterAll(fn() => dropDatabase());
beforeEach(fn() => clearDatabase());

Entities

namespace App\Test;

use App\Entity\Foo;

$entity = repository(Foo::class)->find(123);
$entity->setName('bar');
save($entity);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment