Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
IsolatedTestsTrait helps with running functional/integration tests in isolation, with same start state for each test. It's also optimized, keeping in mind performance concerns.
<?php
namespace XSolve\CashflowBundle\Tests;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Doctrine\DBAL\Driver\PDOSqlite\Driver as PDOSqliteDriver;
require_once(__DIR__ . IsolatedTestsTrait::$kernelRootDir . '/AppKernel.php');
/**
* This trait allows you to operate on clean version (with fixtures) of database
* It will use sqlite database (needs to be configured in config_test.yml!)
*
* You can use it to provide isolation in tests for functional tests or tests
* where you'd like to use database (some integration tests against fixtures)
*/
trait IsolatedTestsTrait
{
protected static $application;
protected static $environment = 'test';
protected static $debug = false;
protected static $fixturesSuffix = '.fixtures';
protected static $fixturesPath = '';
public static $kernelRootDir = '/../../../../app';
/**
* Rebuilds (provides clean instance of database) for each test
*/
public function setUp()
{
parent::setup();
static::rebuildDatabase();
}
/**
* It will run before any setUps and tests in given test suite
* This hook will drop current schema, creat schema and load fixtures
* then it will create a copy of the databse, so it will be used in the future tests in this suite
*/
public static function setUpBeforeClass()
{
static::bootstrapApplication();
}
/**
* After all tests in given test suite it will remove database copy
* Because of this next test suite needs to create its own
*/
public static function tearDownAfterClass()
{
unlink(static::$fixturesPath);
}
/**
* Runs 3 console commands: (all with -q and -e=test)
* doctrine:schema:drop --force
* doctrine:schema:create
* doctrine:fixtures:load --no-interaction
*
* After successful database rebuild, it will copy it for further reuse
*/
protected function rebuildDatabase()
{
$conn = static::$application->getKernel()->getContainer()->get('doctrine.dbal.default_connection');
if (!$conn->getDriver() instanceof PDOSqliteDriver) {
throw new \RuntimeException('It would not work nicely with driver other than PDOSqlite');
}
$dbPath = $conn->getDatabase();
static::$fixturesPath = $dbPath . static::$fixturesSuffix;
if (!file_exists(static::$fixturesPath)) {
// create fresh database (schema and fixtures)
static::runConsole('doctrine:database:create', array('-n' => true));
static::runConsole('doctrine:schema:drop', array('--force' => true));
static::runConsole('doctrine:schema:create', array());
static::runConsole('doctrine:fixtures:load', array('-n' => true, '--fixtures' => __DIR__ . '/../DataFixtures/'));
// copy fresh database to be reused in the future
copy($dbPath, static::$fixturesPath);
} else {
// copy fixtures database to doctrine location
copy(static::$fixturesPath, $dbPath);
}
}
/**
* Bootstraps console application. It's needed to run commands from the code
*/
protected function bootstrapApplication()
{
$kernel = new \AppKernel(static::$environment, static::$debug);
$kernel->boot();
static::$application = new Application($kernel);
static::$application->setAutoExit(false);
}
/**
* It always run with given environment and in quiet mode (no output on the console)
*/
protected function runConsole($command, array $options = array())
{
$options['-e'] = self::$environment;
$options['-q'] = null;
$input = new ArrayInput(array_merge($options, array('command' => $command)));
$result = self::$application->run($input);
if (0 != $result) {
throw new \RuntimeException(sprintf('Something has gone wrong, got return code %d for command %s', $result, $command));
}
return $result;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment