>背景
我在单元测试方面遇到了很多问题,我认为其中很多都与 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
添加后,我的测试通过了。