#php #laravel
#php #laravel
Вопрос:
Я создал тест. Он продолжает сбоить с этой ошибкой:
FAIL TestsFeatureWorkfieldTest
✓ index should be accessible by internal
⨯ index should be accessible by customer
⨯ index should be accessible by employee
⨯ index should not be accessible by guest
IlluminateDatabaseQueryException
SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (`myapp`.`workfields`, CONSTRAINT `workfields_specialization_id_foreign` FOREIGN KEY (`specialization_id`) REFERENCES `specializations` (`id`) ON DELETE CASCADE) (SQL: insert into `workfields` (`specialization_id`, `server_generated`, `updated_at`, `created_at`) values (1, 1, 2020-11-18 18:55:50, 2020-11-18 18:55:50))
Я погуглил и прочитал похожие вопросы здесь и там, но все еще не могу найти подходящий ответ. Я определил и включил сеялки setUp
, соблюдая порядок таблицы. Первый вызов метода тестирования пройден, остальные завершаются неудачно. Я вижу, что ошибка содержит вызов insert, в то время как мой тестовый пример ничего не делает, кроме как получать и считать данные из него. Итак, что здесь произошло?
Test
class WorkfieldTest extends TestCase
{
use RefreshDatabase;
private $internal;
private $customer;
private $employee;
private $count;
public function setUp(): void
{
parent::setUp();
$this->app->make(PermissionRegistrar::class)->registerPermissions();
$this->seed(RolesAndPermissionSeeder::class);
$this->seed(SpecializationSeeder::class);
// dd(Specialization::count()); -> 68
$this->seed(WorkfieldSeeder::class);
$this->internal = User::factory()->create()->assignRole('internal');
$this->customer = User::factory()->create()->assignRole('customer');
$this->employee = User::factory()->create()->assignRole('employee');
$this->count = Workfield::count();
}
public function testIndexShouldBeAccessibleByInternal()
{
$this->actingAs($this->internal, 'api')
->getJson(route('workfields.index'))
->assertOk()
->assertJsonCount($this->count);
}
public function testIndexShouldBeAccessibleByCustomer()
{
$this->actingAs($this->customer, 'api')
->getJson(route('workfields.index'))
->assertOk()
->assertJsonCount($this->count);
}
public function testIndexShouldBeAccessibleByEmployee()
{
$this->actingAs($this->employee, 'api')
->getJson(route('workfields.index'))
->assertOk()
->assertJsonCount($this->count);
}
public function testIndexShouldNotBeAccessibleByGuest()
{
$this->getJson(route('workfields.index'))
->assertUnauthorized();
}
}
Migration
public function up()
{
Schema::create('workfields', function (Blueprint $table) {
$table->id();
$table->foreignId('specialization_id')->constrained()->onDelete('cascade');
$table->boolean('server_generated')->default(false);
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('workfields');
}
Комментарии:
1. Похоже, что, возможно, база данных не очищается после первого теста, и вставка завершается ошибкой при последующих тестах? Однако
RefreshDatabase
черта должна сделать это за вас. У вас есть отдельная база данных, определенная в.env.testing
, да?2. Попробуйте запускать каждый неудачный тест отдельно — например, только первый неудачный тест, только второй неудачный тест, только третий неудачный тест и так далее. Я чувствую, что при возникновении первого сбоя записи БД, заполненные в
setUp
методе, не очищаются, поэтому все последующие тесты также завершаются неудачей. Итак, сначала запуститеphp artisan migrate:fresh
, а затем запустите все тесты отдельно3. @miken32, нет, я не разделял свою базу данных для тестирования, у меня также нет
.env.testing
файла в проекте. Что касаетсяRefreshDatabase
, у меня есть аналогичная мысль, но я не знаю, почему она не работает.4. @Donkarnash, хорошо, позвольте мне попробовать это, и я дам вам знать, если это решит проблему
5. @Donkarnash , я последовал вашему совету и перенес свои семена и фабрики в каждый связанный тестовый пример. все та же ошибка
Ответ №1:
Тестовый файл запускается без ошибок при использовании базы данных sqlite в памяти путем раскомментирования приведенных ниже двух строк в стандарте phpunit.xml
, который поставляется с установкой Laravel
<server name="DB_CONNECTION" value="sqlite"/>
<server name="DB_DATABASE" value=":memory:"/>
Однако при тестировании с MySQL он завершается неудачей, как вы упомянули. Вероятно, проблема в том, что сеялки, запускаемые перед каждым тестом, являются проблемой, поскольку ваши сеялки содержат выборку данных из файлов json, а затем преобразуют их в связанный массив перед запуском Model::create()
. С базой данных в памяти такая проблема не встречается, но с MySQL проблема возникает каким-то образом.
Есть два способа обойти это. Первый, самый простой способ — включить вызов для запуска всех сеялок в DatabaseSeeder
классе, а затем установить значение $seed
свойства true в тестовом классе, где вам нужны исходные данные — больше информации здесь
class WorkfieldTest extends TestCase
{
use RefreshDatabase;
/**
* Indicates whether the database should be seeded before each test.
*
* @var bool
*/
protected $seed = true;
//... tests
}
Другой вариант — запускать сеялки с данными, извлеченными из файлов json, только один раз перед всеми тестами, устанавливая статическую переменную в классе и используя ее для управления тем, что определенные сеялки запускаются только один раз для всех тестов в классе.
class WorkfieldTest extends TestCase
{
use RefreshDatabase;
protected static $initialized = false;
protected function setUp(): void
{
parent::setUp();
$this->app->make(PermissionRegistrar::class)->registerPermissions();
$this->seed(RolesAndPermissionSeeder::class);
if (!static::$initialized) {
$this->seed([SpecializationSeeder::class, WorkfieldSeeder::class]);
static::$initialized = true;
}
}
//... tests
}
Любой из двух вышеуказанных вариантов позволит тестам выполняться с MySQL / Sqlite без каких-либо ошибок.