首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在Laravel8.0测试中刷新数据库时“没有活动事务”

在Laravel8.0测试中刷新数据库时“没有活动事务”
EN

Stack Overflow用户
提问于 2021-04-21 14:45:40
回答 2查看 8.1K关注 0票数 8

使用php 8.0.2和Laravel8.37.0,我正在运行的测试中,每次测试都应该刷新数据库数据,因为每个测试都有相互冲突的数据(由于独特的约束)。

使用带有SQLite的内存数据库,这是可行的,但是当我切换到MySQL (v8.0.23)时,会得到下一个错误:

代码语言:javascript
复制
1) Tests\Feature\Controllers\AuthControllerTest::testSuccessLogin
PDOException: There is no active transaction

而这次之后的测试失败了,原因是数据已经插入,并且测试后没有清除。

我要做的测试是:

代码语言:javascript
复制
<?php

namespace Tests\Feature\Controllers;

use App\Models\User;
use App\Models\User\Company;
use App\Repositories\UserRepository;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class AuthControllerTest extends TestCase
{
    use RefreshDatabase;

    protected array $connectionsToTransact = ['mysql'];

    public function testSuccessLogin(): void
    {
        $this->artisan('migrate-data');

        /** @var User $user */
        $user = User::factory()->create([
            'email' => 'test@website.com'
        ]);

        $this->app->bind(UserRepository::class, function() use ($user) {
            return new UserRepository($user, new Company());
        });

        $loginResponse = $this->post('/api/login', [
            'email' => 'test@website.com',
            'password' => 'password'
        ]);

        $loginResponse->assertStatus(200);
        $loginResponse->assertJsonStructure([
            'data' => [
                'user' => [
                    'name',
                    'surname',
                    'email',
                    'abilities'
                ],
                'token',
            ]
       ]);
    }
}

在执行测试并在数据库中检查之后,数据仍然存在。不管有没有行,protected array $connectionsToTransact = ['mysql'];都给出了同样的结果。

我的phpunit.xml-file看起来像:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
     bootstrap="vendor/autoload.php"
     colors="true"
>
    <testsuites>
        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>
        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>
    </testsuites>
    <coverage processUncoveredFiles="true">
        <include>
            <directory suffix=".php">./app</directory>
        </include>
        <report>
            <html outputDirectory="reports/coverage"/>
        </report>
    </coverage>
    <php>
        <server name="APP_ENV" value="testing"/>
        <server name="BCRYPT_ROUNDS" value="4"/>
        <server name="CACHE_DRIVER" value="array"/>
        <server name="DB_CONNECTION" value="mysql"/>
        <server name="DB_HOST" value="localhost"/>
        <server name="DB_DATABASE" value="mysql_test"/>
        <server name="DB_USERNAME" value="root"/>
        <server name="MAIL_MAILER" value="array"/>
        <server name="QUEUE_CONNECTION" value="sync"/>
        <server name="SESSION_DRIVER" value="array"/>
        <server name="TELESCOPE_ENABLED" value="false"/>
    </php>
</phpunit>

这是众所周知的问题吗?还是我错过了什么?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2022-06-08 09:42:43

我想出来是为了我的案子。我正在使用一个使用迁移逻辑的库来执行数据迁移(因此在我的代码中使用了$this->artisan('migrate-data'); )。

当使用RefreshDatabase特性时,迁移只执行一次,然后启动事务。迁移逻辑对事务做了一些操作,我认为在结束它们之后,会导致我正在发生的错误。

对我起作用的解决方案是在启动事务之前覆盖RefreshDatabase特性以执行数据迁移:

代码语言:javascript
复制
<?php

namespace Tests;

use Illuminate\Contracts\Console\Kernel;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\RefreshDatabaseState;

trait RefreshDatabaseWithData
{
    use RefreshDatabase;

    protected function refreshTestDatabase(): void
    {
        if (! RefreshDatabaseState::$migrated) {
            $this->artisan('migrate:fresh', $this->migrateFreshUsing());
            $this->artisan('migrate-data'); // << I added this line

            $this->app[Kernel::class]->setArtisan(null);

            RefreshDatabaseState::$migrated = true;
        }

        $this->beginDatabaseTransaction();
     }
}

这是只在Laravel 9测试,但我不明白为什么这将不能在以前的版本。

票数 4
EN

Stack Overflow用户

发布于 2022-04-06 04:33:41

我就是这样解决的:

步骤1:扩展连接类和覆盖事务方法:

代码语言:javascript
复制
use Illuminate\Database\Connection as DBConnection;
use Closure;

class Connection extends DBConnection
{

    public function transaction(Closure $callback, $attempts = 1)
    {
        $callback($this);
    }
}

这种覆盖基本上会在内部禁用事务,从而保持当前代码的完整。

步骤2:通过一个ServiceProvider将该具体类绑定到抽象ConnectionInterface (app/Providers/AppServiceProvider.php将是一个很好的地方):

代码语言:javascript
复制
if(config('app.env')==='testing') {
   $this->app->bind(ConnectionInterface::class, Connection::class);
}

请注意,绑定位于针对环境的条件内。我们只希望在运行测试时应用此绑定。

如果您没有使用.env.testing文件进行单元测试,我建议您这样做。它能让一切变得更干净。您只需将.env复制到.env.testing,并且只更新指向测试数据库的DB_CONNECTION和DB_DATABASE常量。

然后在.env.testing中定义phpinit.xml环境:

代码语言:javascript
复制
<php>
   <env name="APP_ENV" value="testing"/>
   <!-- <env name="DB_CONNECTION" value="memory_testing"/> -->
   <!-- <env name="DB_DATABASE" value=":memory:"/> -->
</php>

注意,我注释掉了DB_CONNECTION和DB_DATABASE,因为这些参数将从.env.testing中获取。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67198158

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档