Сбой теста с ошибкой нарушения ограничения целостности, но в первом тестовом примере он прошел. Почему?

#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 без каких-либо ошибок.