Pierstoval
8/3/2016 - 7:04 AM

Some useful classes to use when using an in-memory database that has to be resetted on each kernel boot.

Some useful classes to use when using an in-memory database that has to be resetted on each kernel boot.

<?php

namespace Tests;

use Doctrine\Bundle\FixturesBundle\Command\LoadDataFixturesDoctrineCommand;
use Symfony\Component\BrowserKit\Cookie;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Bundle\FrameworkBundle\Client;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Doctrine\Bundle\DoctrineBundle\Command\Proxy\CreateSchemaDoctrineCommand;

abstract class WebTestCase extends BaseWebTestCase
{
    protected static function bootKernel(array $options = [])
    {
        parent::bootKernel($options);
        static::loadFixtures();
    }

    /**
     * Loads the fixture if possible
     */
    private static function loadFixtures()
    {
        // Here, we create the database each time the kernel is booted.
        // This ensures that potential fixtures or entities manipulations don't interact with other tests.
        if (is_a(static::class, 'Tests\DatabaseTestInterface', true)) {

            $application = new Application(static::$kernel);

            // Create database schema.
            $schemaCommand = new CreateSchemaDoctrineCommand();
            $application->add($schemaCommand);
            $schemaCommand->run(new ArrayInput(['command' => 'doctrine:schema:create']), new ConsoleOutput());

            // If there are fixtures to insert, let's insert them.
            if (is_a(static::class, 'Tests\FixturesTestInterface', true)) {
                /** @var string[] $fixtures */
                $fixtures = static::getFixturesToInsert();

                if (!$fixtures) {
                    return;
                }

                // Convert all classes to file names.
                foreach ($fixtures as $k => $fixture) {
                    if (class_exists($fixture)) {
                        $reflection   = new \ReflectionClass($fixture);
                        $fixtures[$k] = $reflection->getFileName();
                    }
                }

                // Load all desired fixtures.
                $fixturesCommand = new LoadDataFixturesDoctrineCommand();
                $application->add($fixturesCommand);
                $fixturesCommand->run(new ArrayInput([
                    'command'    => 'doctrine:fixtures:load',
                    '--append'   => true,
                    '--fixtures' => $fixtures,
                ]), new ConsoleOutput());
            }
        }
    }
}
<?php

namespace Tests\Controller;

use AppBundle\Entity\Page;
use Tests\DatabaseTestInterface;
use Tests\WebTestCase;

class PortalControllerTest extends WebTestCase implements DatabaseTestInterface
{
    /**
     * Nothing in database except db structure (so no error in functional test)
     */
    public function testHomepageIndex()
    {
        $client = static::createClient();
        
        // This is mandatory, else the kernel will drop memory database after a request.
        // But this can be added in your WebTestCase class anyway (I personally override this everytime).
        $client->disableReboot(); 

        $client->request('GET', '/');

        static::assertEquals(404, $client->getResponse()->getStatusCode());
    }

    /**
     * @see GeneratorController::indexAction
     */
    public function testIndexWithHomepage()
    {
        $client = static::createClient();
        
        // This is mandatory, else the kernel will drop memory database after a request.
        // But this can be added in your WebTestCase class anyway (I personally override this everytime).
        $client->disableReboot(); 

        // Example with OrbitaleCmsBundle:Page entity.
        $page = new Page();
        $page
            ->setHomepage(true)
            ->setHost('localhost')
            ->setTitle('Homepage test')
            ->setContent('<h2>This tag is only here for testing</h2>')
            ->setSlug('homepage-test')
            ->setLocale('fr')
            ->setEnabled(true)
        ;

        /** @var EntityManager $em */
        $em = static::$kernel->getContainer()->get('doctrine')->getManager();
        $em->persist($page);
        $em->flush();

        $crawler = $client->request('GET', '/');
        static::assertEquals(200, $client->getResponse()->getStatusCode(), $client->getResponse()->getContent());

        // Test that inserted page corresponds
        static::assertEquals($page->getTitle(), trim($crawler->filter('#content section article h1')->html()));
        static::assertContains($page->getContent(), trim($crawler->filter('#content section article')->html()));
    }
}
<?php

namespace Tests;

/**
 * Allows inserting fixtures before kernel boot.
 */
interface FixturesTestInterface extends DatabaseTestInterface
{
    /**
     * List of all fixtures to run when kernel is booted, with their class names.
     *
     * @return string[]
     */
    public static function getFixturesToInsert();
}
<?php

namespace Tests\Controller;

use AppBundle\DataFixtures\ORM\PageFixtures;
use Tests\FixturesTestInterface;
use Tests\WebTestCase;

class DefaultEasyAdminTest extends WebTestCase implements FixturesTestInterface
{
    /**
     * Test backend homepage.
     */
    public function testIndex()
    {
        $client = static::createClient();
        
        // This is mandatory, else the kernel will drop memory database after a request.
        // But this can be added in your WebTestCase class anyway (I personally override this everytime).
        $client->disableReboot(); 

        $client->request('GET', '/admin/');

        static::assertEquals(302, $client->getResponse()->getStatusCode(), print_r($client->getResponse()->getContent(), true));
        static::assertEquals('/admin/?action=list&entity=Pages', $client->getResponse()->headers->get('Location'));

        $crawler = $client->followRedirect();

        static::assertEquals(200, $client->getResponse()->getStatusCode(), $crawler->filter('title')->html());
        static::assertEquals('EasyAdmin', $crawler->filter('meta[name="generator"]')->attr('content'));
        
        // This makes sure that the admin "list" table contains at least one element.
        static::assertGreaterThanOrEqual(1, $crawler->filter('#main.content .table-responsive tbody tr[data-id]')->count());
    }

    /**
     * {@inheritdoc}
     */
    public static function getFixturesToInsert()
    {
        return [
            PageFixtures::class,
        ];
    }
}
<?php

namespace Tests;

/**
 * This class allows recreating the database when a kernel is booted.
 * A database test MUST use the WebTestCase::bootkernel method in every test.
 */
interface DatabaseTestInterface
{
    /**
     * Boots the Kernel for this test.
     *
     * @param array $options
     */
    public static function bootKernel(array $options = []);
}