3

How do you load fixtures before running a WebTestCase in Symfony 5 ?

Which methods do you use without installing any additional bundles?

class MyPageControllerTest extends WebTestCase
{
    public function testIndexAction()
    {
        $client = static::createClient();

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

        $this->assertEquals(200, $client->getResponse()->getStatusCode());

        $table = $crawler->filter('.table-emp');

        $this->assertCount(1, $table->filter('tbody tr'));
    }
}
PJX
  • 41
  • 3
  • 1
    Does this answer your question? [How can I load fixtures from functional test in Symfony 2](https://stackoverflow.com/questions/17091772/how-can-i-load-fixtures-from-functional-test-in-symfony-2) – Mike Doe Mar 29 '20 at 18:19

2 Answers2

0

WebTestCase extends Symfony's KernelTestCase which extends PHPUnit's TestCase. PhpUnit has few important methods.

// executed once before the first test
public static void setUpBeforeClass();

// executed once before the last test
public static void tearDownAfterClass();

// executed once for every test before it starts
public void setUp();

// executed once for every test after it ends
public void tearDown();

If you want to have a fresh test database for every test I would create a bash script that executes MySQL file and gets your data in place. Doing it like that gives you so much control over what is happening and where.

I think that to create something reusable and consistent you could create your own Test Case that could extend existing Symfony Web Test case.

This way you will gain one extra abstraction layer in which you can put all the fixture set up and all other extra functionality that might be needed across you web (or API if you do the same for API) tests.

Another solution that would not require using extra class could involve traits and putting your functions there.

I would personally recommend custom parent class with some traits that hold functionality that could be shared between different kinds of tests like


class MyAwesomeParentTestClass 
{
    use AwesomeFeatureTrait;
    use CustomAssertionsTrait;
}

I know you don't want to use extra bundles so this is just an extra suggestion. In case you decide to use bundle, I would recommend using this: Alice.

Robert
  • 1,206
  • 1
  • 17
  • 33
0

Personally, I have a command that deletes my test database and insert my fixtures every time I run phpunit :

in tests/bootstrap.php:

<?php

declare(strict_types=1);

use App\Command\SetupCommand;
use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Dotenv\Dotenv;

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');
}

// https://symfony.com/doc/current/testing.html#set-up-your-test-environment
$kernel = new Kernel(environment: 'test', debug: false);
$kernel->boot();
$application = new Application($kernel);

// Run the app:setup command before running phpunit
$command = new SetupCommand($kernel);
$command->run(new ArrayInput([]), new ConsoleOutput);

If you want my custom command that does all these things:

<?php

declare(strict_types=1);

namespace App\Command;

use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\HttpKernel\KernelInterface;

/**
 * @codeCoverageIgnore
 */
#[AsCommand(
    name: 'app:setup',
    description: 'Setup the app for dev environment',
)]
class SetupCommand extends Command
{
    /**
     * @override
     *
     * {@inheritDoc}
     */
    public function __construct(private readonly KernelInterface $kernel)
    {
        parent::__construct();
    }

    /**
     * @override
     *
     * {@inheritDoc}
     */
    protected function execute(
        InputInterface $input,
        OutputInterface $output
    ): int {
        $io = new SymfonyStyle($input, $output);
        $io->title('Installation assistant');
        $io->warning('This command should not be executed in production !');
        $io->warning('Database suppression...');

        $application = $this->getApplication();
        if (!$application) {
            $application = new Application($this->kernel);
            $this->setApplication($application);
        }

        $returnCode = $application
            ->get('doctrine:database:drop')
            ->run(new ArrayInput([
                '--if-exists' => true,
                '--force' => true
            ]), new NullOutput)
        ;
        if (0 === $returnCode) {
            $io->success('The database has been deleted');

            $io->warning('Database creation...');
            $returnCode = $application
                ->get('doctrine:database:create')
                ->run(new ArrayInput([]), new NullOutput)
            ;
        }


        if (0 === $returnCode) {
            $io->success('The database has been created');

            $io->warning('Database schema creation...');
            $returnCode = $application
                ->get('doctrine:schema:create')
                ->run(new ArrayInput([]), new NullOutput)
            ;
        }

        if (0 === $returnCode) {
            $io->success('The database schema has been created');

            $io->warning('Executing migrations...');

            $args = new ArrayInput([
                '--no-interaction' => true
            ]);

            $args->setInteractive(false);

            $returnCode = $application
                ->get('doctrine:migrations:migrate')
                ->run($args, new NullOutput)
            ;
        }

        if (0 === $returnCode) {
            $io->success('The migrations have been executed');

            $io->warning('Adding test/dummy data...');
            $args = new ArrayInput([
                '--no-interaction' => true,
                '--purge-with-truncate' => true
            ]);
            $args->setInteractive(false);
            $returnCode = $application
                ->get('doctrine:fixtures:load')
                ->run($args, new NullOutput);
        }

        if (0 === $returnCode) {
            $io->success('The data has been created');

            $io->warning('Clearing Symfony cache...');
            $returnCode = $application
                ->get('cache:clear')
                ->run(new ArrayInput([]), new NullOutput);
        }

        if (0 === $returnCode) {
            $io->success('The Symfony cache has been cleared');

            $io->warning('Clearing Doctrine Query cache...');
            $returnCode = $application
                ->get('doctrine:cache:clear-query')
                ->run(new ArrayInput([]), new NullOutput);
        }

        if (0 === $returnCode) {
            $io->success('The Doctrine Query cache has been cleared');

            $io->warning('Clearing Doctrine Result cache...');
            $returnCode = $application
                ->get('doctrine:cache:clear-result')
                ->run(new ArrayInput([]), new NullOutput);
        }

        if (0 === $returnCode) {
            $io->success('The Doctrine Result cache has been cleared');
            if (function_exists('apcu_clear_cache')) {
                $io->warning('Clearing APC cache...');
                apcu_clear_cache();

                $io->success('APCu cache has been cleared');
            }
        }

        return 0;
    }
}

Then I used dmaicher/doctrine-test-bundle to have tests isolated from each other (this bundle basically each wrap tests in a transaction and rollback between each tests). This way, you don't have to load your fixtures between each of your tests.