Codeception: [Yii2] Don't cache DB connection

Created on 3 Nov 2017  路  10Comments  路  Source: Codeception/Codeception

What are you trying to achieve?

Functional testing in Yii2 using a database.
When my application is initialized via a DI container.

What do you get instead?

The Yii2 connector makes assumptions about the way I'm configuring my application; specifically this snippet:
Connector/Yii2.php:63 public function startApp() { .... if (static::$db) { // If the DB connection already exists, make sure to pass it as early as possible // to prevent application from new connection creating during bootstrap $config['components']['db'] = static::$db; }

Details

If the DI container is used it will attempt to merge my application configuration with the configuration generated by the codeception connector.
Normally this only contains a class key, which is fine. But when it writes the components key, and the merge is not recursive (which it is not) it loses a lot of configuration.

To make matters worse; this is really hard to debug since it only fails on the second instantiation (when the connection is cached).

Proposed solution

In my opinion caching the database is an unnecessary optimization. Connecting to the database in a testing environment should at most add a few milliseconds to each test.
In case removing it is not an option I propose to at least make it configurable.

If given a direction I'll make a PR!

Related to: https://github.com/yiisoft/yii2/issues/15089

  • Codeception version: latest
  • PHP Version: N/A
  • Operating System: N/A
  • Installation type: N/A
Yii

All 10 comments

That was quick.

The code you want to remove was merged in 20 hours ago: https://github.com/Codeception/Codeception/pull/4601

Lol; hadn't noticed that...

I think both problems can be solved by not caching the database and using events to start the transaction.

Something like this in the module.
if ($this->config['transaction']) { Event::on(Connection::CLASS,Connection::EVENT_AFTER_OPEN, function(Event $event) use ($counter) { $event->sender->beginTransaction(); codecept_debug(spl_object_hash($event->sender)); }); }
I just can't figure out if we really need to rollback the transactions. I'd expect this to happen automatically since the connection gets closed after each test (which presumably rolls back uncommitted transactions).

On a related note, it seems global event handlers do not get unregistered in:
public function resetPersistentVars() { static::$db = null; static::$mailer = null; \yii\web\UploadedFile::reset(); }
Should add Event::offAll.

@SilverFire ?

Have to say I've made another amazing fix 馃槆
I did not predict this use case...

if ($this->config['transaction']) {
    Event::on(Connection::CLASS,Connection::EVENT_AFTER_OPEN, function(Event $event) use ($counter) {
        $event->sender->beginTransaction();
        codecept_debug(spl_object_hash($event->sender));
    });
}

In this way you will create a transaction, but it will be a different transaction and changes made by previous steps will not be visible.

To be fair, I do not know how to handle this issue.

If the DI container is used it will attempt to merge my application configuration with the configuration generated by the codeception connector.

How does your configuration look like?

The configuration is exactly the same as via a config file; only using the DI container.
So if you take your application config and then tell the DI container:
$container->set(\yii\web\Application::class, $config);
And then pass an empty config to the configFile setting stuff will break.

@SamMousa any idea how to fix both cases?

Not sure; the event method could be hacked to work correctly.
For example we could replace the PDO object after the open event. (You still get multiple yii\db\connection instances but they share a PDO object. By using the event you don't have to worry about the config merge stuff..
Then the cached objects should use a cache key based on their DSN to support multiple / different database configurations.

can access to module in Codeception helper?

...
        $moduleConfig = (new \ReflectionMethod($this->moduleContainer, 'getModuleConfig'))
            ->getClosure($this->moduleContainer)('Yii2');
...
$this->getModule('Yii2')
...

Was this page helpful?
0 / 5 - 0 ratings