vierge-noire / cakephp-fixture-factories

CakePHP Fixture Factories
https://vierge-noire.github.io/
MIT License
85 stars 20 forks source link

Calling fixture factories for a table breaks application code that uses that table downstream #187

Open jamisonbryant opened 2 years ago

jamisonbryant commented 2 years ago

This was the bug at the bottom of a well that I spent many, many days trying to diagnose.

Description of the problem

It appears that if fixture factories touch a table and then downstream application code also does, entity creation does not work for that table in the application code.

How to reproduce

I was able to confirm that the problem occurs as a result of the call to AccessTokensFactory::make(), as refactoring my test data provider to manually construct the entities instead of calling the fixture factory results in all tests in the InnocentTest test class passing once again.

Expected behavior

Whatever magic the fixture factories do to the tables in order to create entities, that magic does not interfere with application code being able to interface with the tables and make entities downstream.

Let me know if all this is clear? I can try to put together an open-source sample app if needed.

pabloelcolombiano commented 2 years ago

@jamisonbryant thank for opening the issue. A sample app would be definitely appreciated, in order to reproduce the issue.

jamisonbryant commented 1 year ago

I haven't had time to put together a sample app, but I do think I have discovered something relevant. Adding these two lines:

// vendor/vierge-noire/cakephp-fixture-factories/src/Factory/EventCollector.php:91
$conn = $table->getConnection();
echo __CLASS__ . ' table connection = ' . PHP_EOL . $conn->configName() . PHP_EOL;

prints "foo" which is one of my database connections instead of "test_foo" as I would expect.

Is it my understanding that when the Cake test suite is running, the defaultConnectionName() of each table is supposed to be prepended by "test_" forming the complete connection name of "test_foo".

I'm not sure what an event collector does, but is there any chance it is not respecting the default cake convention for table names when running unit tests? Could that be the source of this problem?

jamisonbryant commented 1 year ago

Here is more evidence that the BaseFactory's $eventCollector is flipping back and forth between connections:

CakephpFixtureFactories\Factory\EventCollector table connection (top check) = test_saas
CakephpFixtureFactories\Factory\EventCollector table connection (top check) = test_saas
CakephpFixtureFactories\Factory\EventCollector table connection (top check) = test_saas
CakephpFixtureFactories\Factory\EventCollector table connection (top check) = test_saas
CakephpFixtureFactories\Factory\EventCollector table connection (top check) = test_saas
CakephpFixtureFactories\Factory\EventCollector table connection (top check) = test_saas
CakephpFixtureFactories\Factory\EventCollector table connection (top check) = test_saas
CakephpFixtureFactories\Factory\EventCollector table connection (bottom check) = test_saas
CakephpFixtureFactories\Factory\EventCollector table connection (top check) = test_saas
CakephpFixtureFactories\Factory\EventCollector table connection (top check) = test_saas
CakephpFixtureFactories\Factory\EventCollector table connection (top check) = test_saas
CakephpFixtureFactories\Factory\EventCollector table connection (top check) = test_saas
CakephpFixtureFactories\Factory\EventCollector table connection (top check) = test_saas
CakephpFixtureFactories\Factory\EventCollector table connection (top check) = test_saas
CakephpFixtureFactories\Factory\EventCollector table connection (top check) = test_saas
CakephpFixtureFactories\Factory\EventCollector table connection (bottom check) = saas
CakephpFixtureFactories\Factory\EventCollector table connection (top check) = saas
CakephpFixtureFactories\Factory\EventCollector table connection (top check) = saas
CakephpFixtureFactories\Factory\EventCollector table connection (top check) = saas
CakephpFixtureFactories\Factory\EventCollector table connection (top check) = saas
CakephpFixtureFactories\Factory\EventCollector table connection (top check) = saas
CakephpFixtureFactories\Factory\EventCollector table connection (top check) = saas

Top check = EventCollector::getTable() line 78, inside the if block Bottom check = EventCollector::getTable() line 99, right before the function return

jamisonbryant commented 1 year ago

Mar 2023 update: I was able to properly resolve this issue, at long last!

I was right about application code trying to access the table registry at the same time as the unit tests, and using different connections to do so. E.g. my fixture factories would create and persist entities using the test connection, and my app would do the same except using the app connection. For my project, these are two different databases.

The solution is to alias the connections in the test bootstrapper:

// file: tests/bootstrap.php
$testDatasource = 'foo';
try {
    ConnectionManager::get($testDatasource);
    ConnectionManager::alias($testDatasource, 'default');
    ConnectionManager::alias($testDatasource, 'test');
    ConnectionManager::alias($testDatasource, 'other');

    //(new SchemaLoader())->loadSqlFiles(TESTS . 'schema.sql', $testConnection);
    (new Migrator())->run(['connection' => $testDatasource]);
} catch (MissingDatasourceConfigException $e) {
    trigger_error("Cannot run the test suite: missing connection '{$testDatasource}'. Check main config file.");
}

Now, even when the application tries to use the "wrong" database connection, it will go to the correct database.

I'm not sure there's a change to be made to Fixture Factories unless it's a docs clarification, do you agree @pabloelcolombiano?