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
it deletes data before loading as you can see
Yes, I can see that but deleting should be moved to unload. Unload unloads currently all data in correct order but it should delete data aswell. The problem is that during load, the data is not deleted in correct order.
yes , because it is only unloaded in load
to make it possible to debug for developer , see this issues https://github.com/yiisoft/yii2/issues/5176
Postponed. Need to rethink about load/unload and overall testing script to ensure tests can be run smoothly.
@qiangxue it was already discussed many times , what can be improved here ? I see only way if clearing database before and after tests , not sure about time consuming , i think we can live with it
What I meant was that currently unload() is almost useless because we cannot ensure it is always called after each test. As a workaround we are doing "unloading" work in loading stage. But as described in this issue, such workaround has problem in correct call sequence.
Currently i dont understand this
The problem is that during load, the data is not deleted in correct order.
fixtures unload and load data in correct depended order, what we should improve? Only move deleting data to fixture unload
method ?
Assume dependency: A depends on B which depends on 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.
we can do unload
in tearDown
that is always called
What if some test cases fall into infinite loop or recursion and you have to kill the test process? The key is that we should provide a way to run unload()
independently in case the previous test doesn't end gracefully.
ah , i remember this your use - case , ok , will also think about it
We can use pcntl_signal
in our yii\codeception\TestCase
and register it in setUp
and unregister in tearDown
. Sounds like a hack maybe , but not sure how we can else handle interruptions
I think we just need to provide a command to support resetting (cleaning up) database when this happens. In most cases if tests are completed gracefully, we don't need to run this command at all.
but we already have yii fixture/unload-all
or simple yii fixture/unload
yeah, then perhaps we should just revert this change? https://github.com/yiisoft/yii2/commit/205400f
nope , that was made for consistency with other fixtures how they handle unloading , so we should move all fixtures (including ActiveFixture
and so on) unloading to unload
method or leave it as is , since we have yii fixture/unload
command that can unload fixtures in any way - if exception occured before or after test or in test .
So it is more for consistency , logically yes it is better to move it to unload
, that also will enable old feature if developers need it - load more data to already loaded data (call load
multiple times) . Overall logically it is better to clear in unload
. We can reference console command if needed for exception case
I have one ActiveFixture that depends on another. I'm using Codeception and following the Guide and taking hints from yii2-app-advanced.
If there's a fatal error during a test then the rows are left in the table. Re-running the test errors thus:
yii\db\IntegrityException: SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`spinitron2`.`persona`, CONSTRAINT `persona_fk_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`))
The SQL being executed was: DELETE FROM `user`
I guess because unloading the fixtures in yii\codeception\TestCase::setUp()
does not respect the fixture dependencies.
The fix (thanks CeBe) was to add to each of my fixture classes:
public function unload()
{
$this->resetTable();
parent::unload();
}
I can see no other option but having resetTable()
at both load()
and unload()
.
but we already have yii fixture/unload-all or simple yii fixture/unload
As far as I see these commands are useless now, since ActiveFixture::unload()
does not deletes data from table.
Yet another solution can be moving resetTable()
to unload()
and modify FixtureTrait::loadFixtures()
to always call FixtureTrait::unloadFixtures()
:
public function loadFixtures($fixtures = null)
{
$this->unloadFixtures($fixtures);
...
}
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.
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.
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();
}
}
@slinstj thank you for your crutch. :)
@githubjeka :+1:
Is there same solution for Postgres?
class DocumentsFixture extends ActiveFixture
{
public $modelClass = 'app\models\Documents';
public $depends = ['tests\codeception\unit\fixtures\FileFixture'];
public function beforeLoad()
{
parent::beforeLoad();
$this->db->createCommand()->setSql("alter table documents disable trigger all;") -> execute();
}
public function afterLoad()
{
parent::afterLoad();
$this->db->createCommand()->setSql("alter table documents enable trigger all;") -> execute();
}
}
This works I think. Also tried to use $this->tableName
in beforeLoad to not write same code in all fixtures but it returns null
.
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.
There are many opportunities to improve the architecture around fixtures. Fact is that focus lies on other topics. With respect to the Fixture topic and testing. A good alternative is to create records right before you are using them. This is less error prone and easier to understand. In terms of performance, it doesnt really matter but in terms of debugging during tests, a lot of time is saved.
@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.
Does not make sense that resetTable
happens on load
method, it's necessary to move to unload
.
I did not understand why is not possible to do this change?
@leandrogehlen how about failed tests? Unload won't happen and everything would stuck, right?
Exactly, i did this change https://github.com/Codeception/Codeception/pull/4016, but this was not enought,
Because resetTable does not happens on unload
method
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
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.
I would edit documentation myself if u let me to prevent other developers from that confusing.
@nurbeknurjanov , i think that the problem is the feature and not the documentation.
I think that edit the documentation does not solve it
@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.
I have started the work to solve this issue, then i found https://github.com/yiisoft/yii2/blob/master/framework/test/InitDbFixture.php that must be used when we have DB-related tests.
I will open the PR with necessary changes to work without yii\test\InitDbFixture
I don't know what decision will be chosen:
yii\test\InitDbFixture
@leandrogehlen we're using InitDbFixture it's quite helpful in part of turning off foreign checks and removing it is definitely BC.
As of https://github.com/yiisoft/yii2/pull/14001 fixtures are broken for us. If a test errors out, unload() does not get called and the data remains in the DB for the next test. This is causing Integrity Constraint violations. Leaving resetTable() in the load() method solves the problem.
Can you show an example how this error happens?
If a test errors out, unload() does not get called and the data remains in the DB for the next test. This is causing Integrity Constraint violations.
I believe this was solved with
https://github.com/Codeception/Codeception/blob/2.3/src/Codeception/Module/Yii2.php#L324
I can give you the scenario. By example, are you wanting to see code?
We setup a test with the appropriate fixtures to add data to the DB. Not all of the PKs are auto generated and some columns have a unique index. When the first test runs, the fixture data is loaded into the DB, but the test produces an error (any error that is not fatal to the app but it interrupts the test). The subsequent test gets loaded with the fixture data still loaded into the DB which causes the Integrity Constraint issues. Reviewing the DB after the test run, fixture data is still present in the DB.
I can't see the problem this way, and if you pay attention, the unload method is invoked before load
this way the first thing to happens before a test is to clean data.
Maybe you can build a small project with this scenario to show the problem
@leandrogehlen Found the heart of the problem. We have a sizeable project with quite a few tests still using yii2-codeception. Since it is deprecated, changes are not propagating back to the old extension thus breaking our tests.
This wouldn't be a big deal if the documentation didn't rely on the extension: http://www.yiiframework.com/doc-2.0/guide-test-fixtures.html
Additionally, the comments in ActiveFixture indicate that resetTable() will be called on load():
https://github.com/yiisoft/yii2/blob/master/framework/test/ActiveFixture.php#L20-L21
please open a new issue if there is a problem that needs to be fixed.
Most helpful comment
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.
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.