Skip to content

Instantly share code, notes, and snippets.

@maryo
Last active November 2, 2015 16:28
Show Gist options
  • Save maryo/f62b93926c02d28e68d5 to your computer and use it in GitHub Desktop.
Save maryo/f62b93926c02d28e68d5 to your computer and use it in GitHub Desktop.
Symfony2 / Doctrine2 Tests isolation without schema reloading / DB purging + fixtures support
services:
session.storage.mock_array:
class: AcmeBundle\Test\MockArraySessionStorage
public: false
test.client:
class: %test.client.class%
arguments: [@kernel, %test.client.parameters%, @test.client.history, @test.client.cookiejar]
calls: [[disableReboot]]
scope: prototype
<?php
namespace AcmeBundle\Test;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\Loader;
use Doctrine\Common\DataFixtures\ReferenceRepository;
use Doctrine\DBAL\Connection;
use Doctrine\ORM\EntityManager;
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\DependencyInjection\Container;
abstract class IsolatedKernelTestCase extends KernelTestCase
{
/** @var Connection */
private static $connection;
private static $referenceRepository;
private static $fixturesLoaded;
private static $fixturesSavePointCreated;
/**
* {@inheritDoc}
* Creates save point for shared fixtures.
*/
public static function setUpBeforeClass()
{
self::$connection = null;
self::$referenceRepository = null;
self::$fixturesLoaded = false;
self::$fixturesSavePointCreated = false;
if (method_exists(static::class, 'setUpForClass')) {
static::setUpForClass();
}
if (self::$fixturesLoaded) {
self::$connection->createSavepoint('fixtures');
self::$fixturesSavePointCreated = true;
}
}
/**
* {@inheritDoc}
*/
public static function tearDownAfterClass()
{
if (method_exists(static::class, 'tearDownForClass')) {
static::tearDownForClass();
}
parent::ensureKernelShutdown();
}
/**
* {@inheritDoc}
* Keeps the same connection between kernel reboots.
*/
protected static function bootKernel(array $options = [], string $connectionName = 'default')
{
parent::bootKernel($options);
if (self::$connection) {
self::injectConnection(self::$connection, $connectionName);
} else {
self::$connection = self::getConnection($connectionName);
}
self::$connection->beginTransaction();
}
/**
* {@inheritDoc}
* DoctrineBundle automatically closes connections on shutdown and this behavior is not configurable.
* So we need to cheat - remove the connection service IDs from frozen container before it's shutting down.
*/
protected static function ensureKernelShutdown()
{
if (!self::getContainer()) {
return;
}
if (self::$fixturesSavePointCreated) {
self::$connection->rollbackSavepoint('fixtures');
} else {
self::$connection->rollBack();
}
$parameters = &self::getContainerParameters();
$connections = $parameters['doctrine.connections'];
unset($parameters['doctrine.connections']);
parent::ensureKernelShutdown();
$parameters['doctrine.connections'] = $connections;
}
/**
* @param array $classes
* @return ReferenceRepository
*/
protected static function loadFixtures(array $classes)
{
if (!self::getContainer()) {
self::bootKernel();
}
$loader = new ContainerAwareLoader(self::getContainer());
foreach ($classes as $class) {
self::loadFixtureClass($loader, $class);
}
$ormExecutor = new ORMExecutor(self::getEntityManager());
if (self::$referenceRepository) {
$ormExecutor->setReferenceRepository(self::$referenceRepository);
} else {
self::$referenceRepository = $ormExecutor->getReferenceRepository();
}
$ormExecutor->execute($loader->getFixtures(), true);
self::$fixturesLoaded = true;
return self::$referenceRepository;
}
/**
* @return Container|null
*/
protected static function getContainer()
{
return static::$kernel ? static::$kernel->getContainer() : null;
}
protected static function getConnection(string $name = 'default'): Connection
{
return self::getContainer()->get(sprintf('doctrine.dbal.%s_connection', $name));
}
protected static function getEntityManager(): EntityManager
{
return self::getContainer()->get('doctrine.orm.entity_manager');
}
/**
* Injects the given connection into container and replaces registered listeners in it's event manager.
*/
private static function injectConnection(Connection $connection, string $name = 'default')
{
$eventManager = $connection->getEventManager();
$oldListeners = $eventManager->getListeners();
$service = sprintf('doctrine.dbal.%s_connection', $name);
$newListeners = self::getContainer()->get($service)->getEventManager()->getListeners();
foreach ($oldListeners as $event => $listeners) {
foreach ($listeners as $listener) {
$eventManager->removeEventListener($event, $listener);
}
}
foreach ($newListeners as $event => $listeners) {
foreach ($listeners as $listener) {
$eventManager->addEventListener($event, $listener);
}
}
self::getContainer()->set($service, $connection);
}
private static function &getContainerParameters(): array
{
$container = self::getContainer();
$parametersAccessor = function &() use ($container) {
return $container->parameters;
};
$parametersAccessor = $parametersAccessor->bindTo($container, get_class($container));
return $parametersAccessor();
}
private static function loadFixtureClass(Loader $loader, string $class)
{
$fixture = new $class;
if ($loader->hasFixture($fixture)) {
unset($fixture);
return;
}
$loader->addFixture($fixture);
if ($fixture instanceof DependentFixtureInterface) {
foreach ($fixture->getDependencies() as $dependency) {
self::loadFixtureClass($loader, $dependency);
}
}
}
}
<?php
namespace AcmeBundle\Test;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage as BaseMockArraySessionStorage;
class MockArraySessionStorage extends BaseMockArraySessionStorage
{
/**
* {@inheritDoc}
* @see https://github.com/symfony/symfony/issues/13450#issuecomment-147447252
*/
public function setId($id)
{
if ($this->id !== $id) {
parent::setId($id);
}
}
}
@dizda
Copy link

dizda commented Nov 2, 2015

Where do you have the method disableReboot in the test client class?

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