Functional testing in Symfony 5
Asked Answered
C

3

5

This is my symfony project where I am practising functional test and I have such error when I am testing my function.

enter image description here

Here, The section of code in which my error occur:\

<?php

namespace App\Tests;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use App\Entity\Category;

class AdminControllerCategoriesTest extends WebTestCase
{
public function setUp():void
{
    parent::setUp();
    $this->client = static::createClient();

    $this->entityManager = $this->client->getContainer()->get('doctrine.orm.entity_manager');

    $this->entityManager->beginTransaction();

    $this->entityManager->getConnection()->setAutoCommit(false);
}

public function tearDown():void
{
    parent::tearDown();
    $this->entityManager->rollback();
    $this->entityManager->close();
    $this->entityManager = null; //avoid memory leaks
}

public function testTextOnPage()
{
    $crawler = $this->client->request('GET', '/admin/categories');
    $this->assertSame('Categories list', $crawler->filter('h2')->text());
    $this->assertContains('Electronics', $this->client->getResponse()->getContent());
}

public function testNumberOfItems()
{
    $crawler = $this->client->request('GET', '/admin/categories');
    $this->assertCount(21, $crawler->filter('option'));
}
}

Here, my .env, Where I have my database connection:

    # In all environments, the following files are loaded if they exist,
# the latter taking precedence over the former:
#
#  * .env                contains default values for the environment variables needed by the app
#  * .env.local          uncommitted file with local overrides
#  * .env.$APP_ENV       committed environment-specific defaults
#  * .env.$APP_ENV.local uncommitted environment-specific overrides
#
# Real environment variables win over .env files.
#
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
#
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration

###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=018d7408d23791c60854cbb4fc65b667
###< symfony/framework-bundle ###

###> doctrine/doctrine-bundle ###
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
#
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
DATABASE_URL="mysql://root:@127.0.0.1:3306/symf5?serverVersion=mariadb-10.4.11"
# DATABASE_URL="postgresql://symfony:[email protected]:5432/app?serverVersion=13&charset=utf8"
###< doctrine/doctrine-bundle ###

Here, I have following code in my .env.test file:

    # define your env variables for the test env here
KERNEL_CLASS='App\Kernel'
APP_SECRET='$ecretf0rt3st'
SYMFONY_DEPRECATIONS_HELPER=999999
PANTHER_APP_ENV=panther
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots

I don't know what is problem with, I have tried different methods but it doesn't work and I also don't know what wrong with this and what to do.Hope you guys help me sort out my problem. Thank you!

Cog answered 29/11, 2021 at 9:46 Comment(3)
"Unknown database" sounds like the database does not yet exist. What have you tried to resolve the problem? Did you create the database in any way before running the tests?Discuss
@NicoHaase I have my database called symf5 but don't know from where it's denoting unknown database "test_1" . I haven't even used test_1 anywhere in my code and project too.Cog
What else did you try to spot the problem? Does your Doctrine configuration contain anything test related?Discuss
O
10

you have two options:

  1. Create new database for your testing
  2. remove dbname_suffix in your db test which is responsible for giving suffix name for new database test -at config/packages/test/doctrine.yaml -
when@test:
    doctrine:
        dbal:
            # "TEST_TOKEN" is typically set by ParaTest
            dbname_suffix: '_test%env(default::TEST_TOKEN)%'
Overcapitalize answered 12/1, 2022 at 9:53 Comment(2)
That's the right answer. Good job - I didn't notice that after executing composer recipes:update the code block was added. It destroyed the DB connection.Edger
Please don't forget to clear the cache for test env if you decide to move on with the second option: php bin/console cache:clear --env testTopazolite
T
0

I think you might forget to create the .env.test file.

In the docs, you can read that need this file :

https://symfony.com/doc/current/testing.html#customizing-environment-variables

In this file, you will use the right database for your tests.

Tell me if it's working !

Talley answered 29/11, 2021 at 13:29 Comment(5)
Please share more details. Why would a missing .env.test lead to a wrongly resolved database schema?Discuss
Because when you are making Integration Testing, you need a specific file in this environment. As it's written in the docs : "The .env.local file is not used in the test environment, to make each test set-up as consistent as possible." It's always better to have a specific file with custom configuration for the test environment.Talley
Please add all clarification to your question by editing it. If Saddam doesn't want to use any other database, why should he configure the connection differently after all? The configuration from .env looks fine to meDiscuss
Yes let's wait some explanations from @Saddam to see what he wants to do.Talley
Hey @Talley I already have my .env.test file. I tried editing this though it doesn't work. For clearity I am attaching my .env.test file in my question .Please go throuh that and have look what shoul I need change or edit there.Cog
P
0

How looks like your phpunit.xml or does you have one?

We have add a phpunit.xml in the project directory and declare our necessary env variables in the phpunit.xml file, like:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
    colors="true"
    bootstrap="vendor/autoload.php"
    cacheResultFile=".phpunit.cache/test-results"
    executionOrder="depends,defects"
    forceCoversAnnotation="true"
    beStrictAboutCoversAnnotation="true"
    beStrictAboutOutputDuringTests="true"
    beStrictAboutTodoAnnotatedTests="true"
    convertDeprecationsToExceptions="true"
    failOnRisky="true"
    failOnWarning="true"
    verbose="true"
>
    <php>
        <ini name="display_errors" value="1" />
        <ini name="error_reporting" value="1" />
        <env name="APP_ENV" value="test" force="true" />
        <env name="KERNEL_CLASS" value="App\Kernel" />
        <env name="APP_DEBUG" value="false" />
        <env name="DATABASE_URL" value="sqlite:///:memory:" force="true" />
        <var name="DB_DBNAME" value="app" />
    </php>

    <testsuites>
        <testsuite name="Test Suite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>

    <coverage cacheDirectory=".phpunit.cache/code-coverage" processUncoveredFiles="true">
        <include>
            <directory suffix=".php">src</directory>
        </include>
        <exclude>
            <directory>src/Entity</directory>
            <directory>src/Repository</directory>
            <file>src/Kernel.php</file>
        </exclude>
    </coverage>
    
    <listeners>
        <listener class="Symfony\Bridge\Phpunit\SymfonyTestsListener" />
    </listeners>

    <extensions>
        <extension class="Symfony\Component\Panther\ServerExtension" />
    </extensions>
</phpunit>

For setup all functional tests we init the database schema on a tests/Framework/FunctionalTestCase.php

<?php 

namespace App\Tests\Framework;

use App\Tests\Framework\DatabaseUtil\InitDatabase;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class FunctionalTestCase extends WebTestCase
{
    protected EntityManagerInterface|null $entityManager = null;
    private KernelBrowser|null $client = null;

    protected function setUp(): void 
    {
        parent::setUp();
        
        self::ensureKernelShutdown();
        $this->client = static::createClient();

        InitDatabase::updateSchema($this->client);
        $this->entityManager = $this->client->getContainer()
            ->get('doctrine')
            ->getManager();
    }

    protected function getClientFromParent(): KernelBrowser
    {
        return $this->client;
    }
}

And the tests/Framework/DatabaseUtil/InitDataBase.php:

<?php

namespace App\Tests\Framework\DatabaseUtil;

use Doctrine\ORM\Tools\SchemaTool;

class InitDatabase
{
    public static function updateSchema(object $kernel): void
    {
        $entityManager = $kernel->getContainer()->get('doctrine.orm.entity_manager');
        $metaData = $entityManager->getMetadataFactory()->getAllMetadata();
        $schemaTool = new SchemaTool($entityManager);
        $schemaTool->updateSchema($metaData);
    }
}

Using

We using this FunctionalTestCase in our ControllerTests like:

<?php

namespace App\Tests\Controller\AnyController;

use App\Tests\Framework\FunctionalTestCase;
use App\Entity\User;
use App\TestsDataFixture\UserFixture;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\Persistence\ObjectManager;

class AnyControllerTest extends FunctionalTestCase
{
    private User $user;
    private User $entityUser;
    private KernelBrowser $client;
    
    public function setUp(): void
    {
        parent::setUp();
        
        $userFixture = new UserFixture();
        $this->user = $userFixture->load($this->entityManager);
        $this->entityUser = $this->entityManager->getRepository(User::class)->findAll()[0];

        $this->client = $this->getClientFromParent();
    }

    public function tearDown(): void
    {
        parent::tearDown();
        $this->delete([$this->entityUser], $this->entityManager);
    }

    public function testLoginSuccessful(): void 
    {
        $payload = [
            'username' => $this->user->getEmail(),
            'password' => $this->user->getPassword()
        ];
        
        $this->client->loginUser($this->user);

        $this->client->request(
            'POST',
            '/auth/login',
            [],
            [],
            [
                'Content-Type' => 'application/json'
            ],
            json_encode($payload)
        );

        $response = $this->client->getResponse()->getContent();
        $data = json_decode($response, true);
        
        $this->assertResponseIsSuccessful();
        $this->assertIsString($data['token']);
    }

    private function deleteFromDatabase(array|Collection $entities, ObjectManager $manager): void 
    {
        $connection = $manager->getConnection();
        $databasePlatform = $connection->getDatabasePlatform();

        if ($databasePlatform->supportsForeignKeyConstraints()) {
            $connection->query('SET FOREIGN_KEY_CHECKS=0');
        }

        foreach($entities as $entity) {
            try {
                $query = $databasePlatform->getTruncateTableSQL(
                    $manager->getClassMetadata(get_class($entity))->getTableName()
                );
                $connection->executeUpdate($query);
            } catch(TableNotFoundException $exception) {
                // do nothing
            }
        }
        
        if ($databasePlatform->supportsForeignKeyConstraints()) {
            $connection->query('SET FOREIGN_KEY_CHECKS=1');
        }
    }
}

The UserFixture is a normal DataFixture with a load method to generate a FakeUser like in this example: https://symfony.com/bundles/DoctrineFixturesBundle/current/index.html

You can put the private delete method in a trait for using in more than one controller.

In this example we use the in-memory sqlite database, but you can change the DATABASE_URL in phpunit also to a MariaDB DSN.

Parados answered 26/12, 2021 at 8:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.