从 sqlite 切换到 mysql 进行测试,所有测试都被破坏了



>背景

我在单元测试方面遇到了很多问题,我认为其中很多都与 sqlite 有关。由于我的本地环境和生产环境都是 mysql,因此我想在尽可能相似的条件下进行测试。

我正在使用Laravel Sail,我找到了有关如何在单独的docker容器中创建测试数据库进行测试的指南。

我按照步骤操作,在解决了几个初始错误后,我让它工作了......有点。我所有的测试现在都失败了,但我认为这更多地与我如何设置测试有关,或者我的一些误解。

TL;我上面提到的指南中的DR是您在phpunit.xml文件中添加一条记录,所以现在我的看起来像这样:

<php>
<server name="APP_ENV" value="testing"/>
<server name="BCRYPT_ROUNDS" value="4"/>
<!-- <server name="DB_CONNECTION" value="testing"/>   <--guide says you don't need
<server name="DB_DATABASE" value="testing"/> -->      <--guide says you don't need.
<server name="CACHE_DRIVER" value="array"/>
<server name="MAIL_DRIVER" value="array"/>
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
<env name="DB_HOST" value="testdb"/>                  <--this is what's new
</php>

然后你修改你的docker-compose.yml文件,我做了,我的现在看起来像这样:

mysql:
image: 'mysql/mysql-server:8.0'
ports:
- '${FORWARD_DB_PORT:-3306}:3306'
environment:
MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
MYSQL_ROOT_HOST: "%"
MYSQL_DATABASE: '${DB_DATABASE}'
MYSQL_USER: '${DB_USERNAME}'
MYSQL_PASSWORD: '${DB_PASSWORD}'
MYSQL_ALLOW_EMPTY_PASSWORD: 1
volumes:
- 'sail-mysql:/var/lib/mysql'
networks:
- sail
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-p${DB_PASSWORD}"]
retries: 3
timeout: 5s
testdb:
image: 'mysql/mysql-server:8.0'
tmpfs: /var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
MYSQL_ROOT_HOST: "%"
MYSQL_DATABASE: '${DB_DATABASE}'
MYSQL_USER: '${DB_USERNAME}'
MYSQL_PASSWORD: '${DB_PASSWORD}'
MYSQL_ALLOW_EMPTY_PASSWORD: 1
networks:
- sail

我怀疑这部分不是问题,但我把它放在那里作为背景。

失败

我的所有测试(第一个除外)在尝试插入用户表时都失败并出现相同的错误。users 表有一个role_id外键,从错误来看,调用用户工厂时似乎没有填充此表。这是错误:

SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails 

测试详情

我在 Laravel 中使用了RefreshDatabase特征,我所有的测试都从设置方法开始。它们都非常相似:

public function setup(): void
{
parent::setUp();
$this->setupFaker();
$this->seed();
$this->user = User::factory()->create();
$this->client = Client::factory()->create();
$this->location = Location::factory()->create();
}

我有一个主要的DatabaseSeeder文件,用于设置所有参考数据/查找表以及不会更改但需要的东西。该文件包含以下run方法:

public function run()
{
Schema::disableForeignKeyConstraints();
$this->call([
RoleSeeder::class,        <--roles are seeded here
LocationStatusSeeder::class,
TaskStatusSeeder::class,
TaskTypeSeeder::class,
UserSeeder::class,
USStateSeeder::class,
FrequencyTypeSeeder::class,
SmsTypeSeeder::class,
TransactionTypeSeeder::class,
DefaultTaskSeeder::class,
]);
Schema::enableForeignKeyConstraints();
}

我的理解是,当setup方法运行时,它会调用$this->seed(),最终运行DatabaseSeeder类。

这是我ClientTest文件的较大片段。test_client_page_is_rendered通过,但文件中的所有其他测试都失败并出现上述错误。这也是整体测试中的第一组测试,因此某些东西允许它在第一个测试中工作并失败所有其他测试:


protected $user;
protected $client;
protected $location;
public function setup(): void
{
parent::setUp();
$this->setupFaker();
$this->seed();
$this->user = User::factory()->create();
$this->client = Client::factory()->create();
$this->location = Location::factory()->create();
}

public function test_client_page_is_rendered()
{
$user = User::first();
$client = Client::first();
$this->actingAs($user);
$response = $this->get('/clients', ['id' => $client['id']]);
$response->assertStatus(200);
}
public function test_client_can_be_created()
{
$user = User::first();
Livewire::actingAs($user);
$formData = [
'client.company_name' => $this->faker->company,
'client.address' => $this->faker->streetAddress,
'client.city' => $this->faker->city,
'client.state' => $this->faker->StateAbbr,
'client.zip_code' => $this->faker->numerify('#####'),
];
$client = Livewire::test(ClientCreateOrUpdate::class, $formData)->call('save');

$this->assertNotNull($client);
}

以前我在测试方法中引用了$this->client等,但已经切换到仅获取第一条记录作为解决此问题的尝试。这就是为什么我在设置方法中分配了属性,但在测试方法中未使用。

我知道使用内存中的sqlite,每个测试本质上都是重新填充数据库,但是我认为现在我已经切换了它,所以需要更改有关我的设置的某些内容,我只是无法弄清楚它是什么。

对于将来可能遇到此问题的任何人,我能够通过将以下内容添加到我的TestCase文件中来解决此问题:

/**
* Indicates whether the default seeder should run before each test.
*
* @var bool
*/
protected $seed = true;

以下是文档引用的内容:https://laravel.com/docs/9.x/database-testing#running-seeders

添加后,我的测试通过了。

最新更新