sqlite RefreshDatabase在测试中锁定数据库

vwkv1x7d  于 2022-11-24  发布在  SQLite
关注(0)|答案(1)|浏览(163)

我正在尝试使用Pest在我的Laravel 8项目中引入测试。该项目使用自己的数据库,但也连接到另一个数据库。后者与不同的系统共享,通过在config/database.php中设置两个连接来完成,即使两个数据库都驻留在生产环境中的同一个MariaDB示例中。
配置/数据库. php:

(...)
    'default' => env('DB_CONNECTION', 'mysql'),
    'another' => 'another',

    'connections' => [
        'mysql' => [
            'driver' => env('DATABASE_DRIVER', 'mysql'),
            'url' => env('DATABASE_URL'),
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'laravel'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'prefix_indexes' => true,
            'strict' => true,
            'engine' => 'innodb',
            'options' => extension_loaded('pdo_mysql') ? array_filter([
                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
            ]) : [],
        ],
        'dummyname' => [
            'driver' => env('DATABASE_DRIVER', 'mysql'),
            'url' => env('DATABASE_URL'),
            'host' => env('DB_HOST_ANOTHER', '127.0.0.1'),
            'port' => env('DB_PORT_ANOTHER', '3306'),
            'database' => env('DB_DATABASE_ANOTHER', 'another'),
            'username' => env('DB_USERNAME_ANOTHER', 'forge'),
            'password' => env('DB_PASSWORD_ANOTHER', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'prefix_indexes' => true,
            'strict' => true,
            'engine' => null,
            'options' => extension_loaded('pdo_mysql') ? array_filter([
                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
            ]) : [],
            'modes' => [
                'STRICT_TRANS_TABLES',
                'NO_ZERO_IN_DATE',
                'NO_ZERO_DATE',
                'ERROR_FOR_DIVISION_BY_ZERO',
                'NO_AUTO_CREATE_USER',
                'NO_ENGINE_SUBSTITUTION'
            ],
        ],
(...)

由于表名不重叠,并且我使用MySQL数据库中的透视表在它们之间创建了一个关系,因此我决定在测试中使用单个SQLite数据库,方法是设置phpunit.xml,如下所示:

<php>
    <server name="APP_ENV" value="testing"/>
    <server name="BCRYPT_ROUNDS" value="4"/>
    <server name="CACHE_DRIVER" value="array"/>
    <server name="DB_DATABASE" value="/tmp/tests.sqlite"/>
    <server name="DB_DATABASE_ANOTHER" value="/tmp/tests.sqlite"/>
    <server name="DATABASE_DRIVER" value="sqlite"/>
    <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>

有两个名为ProjectPollProject的模型。第一个是Laravel的,另一个的表在另一个数据库中:

mysql.projects:
    - id
    - ...

another.poll_projects
    - id
    - ...

mysql.project_poll_project
    - project_id
    - poll_project_id
    (UNIQUE index on both)
    (FOREIGN KEY constraint on the first one)

关系定义如下:

class Project extends Model
{
    protected $connection = 'mysql';

    public function pollProjects() {
        $database = $this->getConnection()->getDatabaseName() . '.';
        if ($database == '/tmp/tests.sqlite.') $database = '';

        return $this->belongsToMany(PollProject::class, $database . 'project_poll_project', 'project_id', 'poll_project_id');
    }
}

class PollProject extends Model
{
    // database table settings
    protected $connection = 'another';
    protected $table = 'poll_projects';

    public function projects() {
        $foreign_connection = 'mysql';
        return $this->belongsToMany(Project::class, "$foreign_connection.project_poll_project", 'poll_project_id', 'project_id');
    }
}

我希望基于用户被分配到项目测试权限我通过将RefreshDatabase附加到TestCase来刷新数据库
测试/测试案例. php:

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication, RefreshDatabase;
}

因为Project可以有多个PollProjects,所以我将创建对象的任务委托给工厂,然后尝试将PollProject附加到Project
测试/功能/投票项目角色测试. php:

(...)
beforeEach(function () {
    PollProject::factory()->count(2)->create();
    $this->accessiblePollProject = PollProject::find(1);
    $this->forbiddenPollProject = PollProject::find(2);
    
    Project::factory()->count(1)->create();
    $this->project = Project::find(1);
    $this->project->pollProjects()->attach($this->accessiblePollProject);
    die('It will not reach this point');
(...)
});

it('allows to list templates', function () {
    expect(true)->toBeTrue();
});

创建了这个测试用例和其他测试用例(使用Pest\Laravel\get或test can())后,运行测试套件会导致SQLite的锁定数据库错误:

root@5ea0190680a9:/var/www# php artisan test

   WARN  Tests\Unit\TemplatePolicyTest
  ✓ it allows superadmins to do anything
  ! it disallows viewing templates → This test did not perform any assertions  /var/www/vendor/pestphp/pest/src/Factories/TestCaseFactory.php(223) : eval()'d code:5
  ! it disallows updating templates → This test did not perform any assertions  /var/www/vendor/pestphp/pest/src/Factories/TestCaseFactory.php(223) : eval()'d code:5
  ! it disallows destroying templates → This test did not perform any assertions  /var/www/vendor/pestphp/pest/src/Factories/TestCaseFactory.php(223) : eval()'d code:5
  ! it disallows ... templates → This test did not perform any assertions  /var/www/vendor/pestphp/pest/src/Factories/TestCaseFactory.php(223) : eval()'d code:5

   PASS  Tests\Feature\PagesStatusesTest
  ✓ get '/' → assertRedirect '/home'
  ✓ get '/home' → assertRedirect '/login'
  ✓ get '/login' → assertSuccessful 
  ✓ get '/admin/user' → assertRedirect '/login'

   FAIL  Tests\Feature\PollProjectRolesTest
  ⨯ it allows to list templates

  ---

  • Tests\Feature\PollProjectRolesTest > it allows to list document templates
   Illuminate\Database\QueryException 

  SQLSTATE[HY000]: General error: 5 database is locked (SQL: insert into "project_poll_project" ("poll_project_id", "project_id") values (1, 1))

  at vendor/laravel/framework/src/Illuminate/Database/Connection.php:712
    708▕         // If an exception occurs when attempting to run a query, we'll format the error
    709▕         // message to include the bindings with SQL, which will make this exception a
    710▕         // lot more helpful to the developer instead of just the database's errors.
    711▕         catch (Exception $e) {
  ➜ 712▕             throw new QueryException(
    713▕                 $query, $this->prepareBindings($bindings), $e
    714▕             );
    715▕         }
    716▕     }

      +8 vendor frames 
  9   tests/Feature/PollProjectRolesTest.php:27
      Illuminate\Database\Eloquent\Relations\BelongsToMany::attach(Object(App\PollProject))

  Tests:  1 failed, 4 risked, 5 passed
  Time:   63.85s

我尝试使用单独的数据库文件,但结果是:
SQL状态[HY 000]:一般错误:1无此表:项目调查项目
与Laravel相同位置的错误在查询MySQL时无法访问数据库another:memory:作为数据库位置也导致no such table。尽管两个数据库都设置为:memory:,但它们似乎是单独的示例。As explained
使用URI格式指定共享高速缓存(如您所使用的)将导致同一进程中的所有连接使用同一数据库
这是否意味着有多个进程处理连接?或者Laravel使用某种连接池?当我从已经应用了迁移的TestCase中删除RefreshDatabase trait时,测试设置通过了:

root@5ea0190680a9:/var/www# php artisan test

   WARN  Tests\Unit\DocumentTemplatePolicyTest
  ✓ it allows superadmins to do anything
  ! it disallows viewing templates → This test did not perform any assertions  /var/www/vendor/pestphp/pest/src/Factories/TestCaseFactory.php(223) : eval()'d code:5
  ! it disallows updating templates → This test did not perform any assertions  /var/www/vendor/pestphp/pest/src/Factories/TestCaseFactory.php(223) : eval()'d code:5
  ! it disallows destroying templates → This test did not perform any assertions  /var/www/vendor/pestphp/pest/src/Factories/TestCaseFactory.php(223) : eval()'d code:5
  ! it disallows ... templates → This test did not perform any assertions  /var/www/vendor/pestphp/pest/src/Factories/TestCaseFactory.php(223) : eval()'d code:5

   PASS  Tests\Feature\PagesStatusesTest
  ✓ get '/' → assertRedirect '/home'
  ✓ get '/home' → assertRedirect '/login'
  ✓ get '/login' → assertSuccessful 
  ✓ get '/admin/user' → assertRedirect '/login'
It will not reach this point
xytpbqjk

xytpbqjk1#

我意识到,虽然可能没有连接池,但必须至少涉及两个连接,因为我自己在config/database.php中定义了这些连接。我通过在使用连接的地方参数化连接名称来解决这个问题,因此在测试中只使用一个连接。
我在phpunit.xml文件中创建了一个附加变量,因此只使用默认连接:

<server name="DB_CONNECTION_ANOTHER" value="mysql"/>

在我更改的配置文件中:

'another' => 'another',

'another' => env('DB_CONNECTION_ANOTHER', 'another'),

每次出现DB::connection('another')Schema::('another')时,分别变更为DB::connection(config('database.another'))Schema::connection(config('database.another'))
由于模型也定义了private $connection = 'another';,而且这个属性是静态的,所以我必须在那里定义一个允许动态值的构造函数:

public function __construct(array $attributes = [])
    {
        parent::__construct($attributes);
        $this->connection = config('database.another');
    }

这为我解决了问题。

相关问题