Symfony 5 中的功能测试

5
这是我的Symfony项目,我正在练习功能测试,但在测试我的函数时出现了这样的错误。

enter image description here

这里是出错的代码段:

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

这里是我的 .env 文件,里面存放着我的数据库连接信息:

    # 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:ChangeMe@127.0.0.1:5432/app?serverVersion=13&charset=utf8"
###< doctrine/doctrine-bundle ###

这里,我在我的.env.test文件中有以下代码:

    # 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

我不知道问题出在哪里,我尝试了不同的方法,但都没有成功,而且我也不知道出了什么问题以及该怎么做。希望你们能帮我解决问题。

谢谢!


"Unknown database"听起来像是数据库尚不存在。您尝试过什么来解决这个问题?在运行测试之前是否以任何方式创建了数据库? - Nico Haase
@NicoHaase 我有一个名为symf5的数据库,但不知道它从哪里表示未知的数据库“test_1”。我甚至在我的代码和项目中都没有使用过test_1。 - Saddam Hawari
你还尝试了什么来找出问题?你的Doctrine配置中是否包含任何与测试相关的内容? - Nico Haase
3个回答

9

你有两个选项:

  1. 为你的测试创建一个新数据库
  2. config/packages/test/doctrine.yaml中删除负责为新数据库测试提供后缀名称的dbname_suffix -
when@test:
    doctrine:
        dbal:
            # "TEST_TOKEN" is typically set by ParaTest
            dbname_suffix: '_test%env(default::TEST_TOKEN)%'

1
那是正确的答案。干得好 - 我没有注意到在执行 composer recipes:update 后,代码块被添加了。它破坏了数据库连接。 - Purzynski
如果您决定选择第二个选项,请不要忘记清除测试环境的缓存:php bin/console cache:clear --env test - Sergii Dolgushev

0

你的phpunit.xml长什么样子?或者说,你有这个文件吗?

我们在项目目录中添加了一个phpunit.xml文件,并在其中声明了必要的环境变量,例如:

<?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>

为了设置所有功能测试,我们在 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;
    }
}

还有测试/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);
    }
}

使用

我们在控制器测试中使用这个FunctionalTestCase,例如:

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

UserFixture是一个具有加载方法的普通DataFixture,可生成类似于此示例中的FakeUser:

https://symfony.com/bundles/DoctrineFixturesBundle/current/index.html

您可以将私有删除方法放在trait中,以便在多个控制器中使用。

在此示例中,我们使用内存中的sqlite数据库,但您也可以在phpunit中将DATABASE_URL更改为MariaDB DSN。


0

请分享更多细节。为什么缺少 .env.test 会导致错误解析数据库模式? - Nico Haase
因为在进行集成测试时,您需要在该环境中使用特定的文件。正如文档中所写: “在测试环境中不使用.env.local文件,以使每个测试设置尽可能一致。” 最好为测试环境准备一个具有自定义配置的特定文件。 - A.DREY
请通过编辑问题添加所有澄清信息。如果萨达姆不想使用其他数据库,那么毕竟他为什么要配置不同的连接呢?从.env的配置上看,一切都很好。 - Nico Haase
是的,让我们等待来自@Saddam的解释,看他想做什么。 - A.DREY
嘿 @A.DREY,我已经有了我的 .env.test 文件。我尝试编辑它,但它不起作用。为了清晰起见,我在我的问题中附上了我的 .env.test 文件。请查看一下,看看我需要更改或编辑什么。 - Saddam Hawari

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接