yii2: ActiveFixture does not delete actual data in unload() method

ActiveFixture unload does not call resetTable() in unload() and this causes trouble when you have dependant fixtures with foreign key constraints. As ActiveFixture tries to loadFixtures(), which calls resetTable() it fails as the dependant data is not deleted. The issue description is related with #5441

About this issue

  • Original URL
  • State: closed
  • Created 10 years ago
  • Comments: 41 (35 by maintainers)

Commits related to this issue

Most upvoted comments

Hi, If you want unload fixture in load function you should care about depends. I read documentation that says unload remove all fixture data.

Take a look at: https://github.com/yiisoft/yii2/blob/master/framework/test/FixtureTrait.php#L33 https://github.com/yiisoft/yii2/blob/master/framework/test/Fixture.php#L36

You should clean up this thing, because you implement a lot of ways to do the same thing.

  • array order in fixtures() on FixtureTrait
  • depends property in yii/test/Fixture

And still it does not WORK! Do you want provide reliable solution OR NOT?

I do not know if you know what you talking about. Unload MUST remove data after test. If you want have a debug data you should archive it, like a html pages.

For example you can simple make a database DUMP if there is any fail or error.

@leandrogehlen This issue is from 2014. Still is not solved. And is not going to be solved soon. And untill this is not solved people are reading the documentation and trying to implement this function. Off course it is not working, and we think that we did something wrong in fact that documentation feeds us not correct information. Or it would be solved, but I doubt it would be soon, Or untill that moment documentation I guess should have some note information letting us to know, that this function is not prepared.

Hey guys I have a suggestion. I spent hours confusing why unload doesn’t work. Then I found this post. If you gonna consider that unload is useless, maybe then it would be better to edit documentation that this method is nor realized, on discussing.

It says in documentation that unload clears data, but in fact it doesn’t. It really confuses and lost energy thinking that I did wrong test.

Or I would edit documentation myself if u let me to prevent other developers from that confusing.

The resetTable() workaround cited by @tom-- does not work for me.

This is my mysql workaround and should be added to each fixture model that has some dependant table:


<?php
namespace tests\codeception\common\fixtures;

use yii\test\ActiveFixture;

class VariationFixture extends ActiveFixture
{
    public $modelClass = 'common\models\Variation';

    public function beforeLoad() {
        parent::beforeLoad();
        $this->db->createCommand()->setSql('SET FOREIGN_KEY_CHECKS = 0')->execute();
    }

    public function afterLoad() {
        parent::afterLoad();
        $this->db->createCommand()->setSql('SET FOREIGN_KEY_CHECKS = 1')->execute();
    }
}

What do you think about adding one more field to Fixture (public $unloadFirst = [] or something like that) which will contain links to all the dependent fixtures. Using this field it will be not too complicated to build unloading mechanism actually clearing all the tables in proper order and without any foreign key violations.

We don’t need any hard changes in loading fixtures - just add check if we need to unload data in any dependent tables. And of course we could return resetTable() to unload() and be sure unloading will be performed in correct order.

For example, fixture OrdersFixture is dependent of UserFixture. In database schema we also have foreign key not allowing us to have Orders not belonging to any User. We could build these fixtures like that:

class OrderFixture extends ActiveFixture
{
    public $modelClass = 'common\models\Order';
    public $depends = ['tests\codeception\unit\fixtures\UserFixture'];
}

class UserFixture extends ActiveFixture
{
    public $modelClass = 'common\models\User';
    public $unloadFirst = ['tests\codeception\unit\fixtures\OrderFixture'];
}

Case 1: We call $UserFixture->load(), it will check it’s own $unloadFirst array and perform unload with actual clearing data for all fixtures there - before calling $UserFixture->unload(). In this case - call $OrderFixture->unload() prior to any actual user data loading. So after this we’ll have data in users table and empty orders table.

Case 2: We call $OrderFixture->load(), it will check it’s own $depends, will call $UserFixture->load, which will do all the same as described in Case 1 (first unload orders, then load users). And after that we can load all orders data as expected - into already emptied table. Both fixtures loaded and no any problems with foreign key violations.

Can we enhance Fixture this simple way or I missed something? This also can be used to make more complex depedencies (inherited dependencies, multiple dependencies). I see the only one significant consequence - we need to make some kind of cross-linking between fixtures with such dependencies and it also can make difficult the tracking of fixtures loading/unloading order. But I don’t think it’s too complicated for developers who knows how to use foreign keys in their database. Those who don’t use foreign keys with restrictions, may continue to use $depends field only.

how about failed tests?

class CategoryFixtrue extends ActiveFixture 
{
     public $modelClass = 'app\models\Category';
}

class ProductFixtrue extends ActiveFixture 
{
     public $modelClass = 'app\models\Product';
     public $depends = ['app\tests\fixtures\CategoryFixtrue'];
}

How the unload method don’t execute resetTable, after tests the tables will have records, in the next execution, the resetTable will happens on load method and the first command will be:

delete from `category`

if table product have fk we will have fk constraint exception

Assume dependency: A depends on B which depends on C

  • Load: C, B, A
  • Unload: A, B, C

Yes, I’m thinking perhaps we should move data deleting code back to unload() and think of a way to enforce unload() is always called (or can be called later) even if the test quits in the middle somehow. Not quite sure yet.

@leandrogehlen we’re using InitDbFixture it’s quite helpful in part of turning off foreign checks and removing it is definitely BC.

@Kolyunya what do you mean? We’re using fixtures with depedences and everything seems fine. @dynasource imho, saving and deleting data is not what I want to worry about during testing process. So maybe fixtures are not so comfortable to use, but definetely bring the write way to testing flow.