craftcms / cms

Build bespoke content experiences with Craft.
https://craftcms.com
Other
3.2k stars 617 forks source link

transaction option in codeception doesn't roll back database changes made in tests. #7615

Open myleshyson opened 3 years ago

myleshyson commented 3 years ago

Description

I'm noticing that even though I'm setting transaction: true in codeception.yml, database operations made within tests aren't rolled back after the test is done. Here's the test file i'm running. There's no modules or plugins enabled for this test instance.

<?php

namespace tests\integration;

use Codeception\Test\Unit;
use craft\elements\Entry;
use tests\fixtures\LocationFixture;
use tests\fixtures\ProviderFixture;

class GmbModuleTest extends Unit
{
    /**
     * @var \UnitTester
     */
    protected $tester;

    public function testCreatedLocationTriggersQueue()
    {
        $section = \Craft::$app->getSections()->getSectionByHandle('locations');
        $location = new Entry();
        $location->sectionId = $section->id;
        $location->title = 'Some New Location';
        $location->typeId = $section->getEntryTypes()[0]->id;
        $location->enabled = true;
        \Craft::$app->getElements()->saveElement($location);
    }
}
actor: Tester
paths:
  tests: tests
  log: tests/_output
  data: tests/_data
  support: tests/_support
  envs: tests/_envs
bootstrap: _bootstrap.php
params:
  - tests/.env
modules:
  config:
    \craft\test\Craft:
      configFile: "tests/_craft/config/test.php"
      entryUrl: "http://host.docker.internal/index.php"
      projectConfig: {
        folder: "config/project",
        reset: false
      }
      migrations: []
      plugins: []
      cleanup: true
      transaction: true
      dbSetup: { clean: true, setupCraft: true }
# Codeception Test Suite Configuration
#
# Suite for unit or integration tests.

actor: UnitTester
modules:
    enabled:
        - \craft\test\Craft
        - Asserts
        - \Helper\Unit
docker-compose exec php ./vendor/bin/codecept run integration

Steps to reproduce

  1. Write a test in which it creates a new entry.
  2. See that after the test is done running, the entry still exists within the database.

Additional info

myleshyson commented 3 years ago

Currently my workaround for this is a custom extension that starts a transaction before a test and rolls everything back afterwards.

tests/_support/TransactionExtension.php

<?php

use Codeception\Events;
use yii\db\Transaction;

class TransactionExtension extends \Codeception\Extension
{
    /**
     * @var Transaction
     */
    protected $transaction;

    public static $events = array(
        Events::TEST_BEFORE => 'beforeTest',
        Events::TEST_AFTER => 'afterTest',
    );

    // methods that handle events
    public function beforeTest(\Codeception\Event\TestEvent $e) {
        $this->transaction = \Craft::$app->getDb()->beginTransaction();
    }

    public function afterTest(\Codeception\Event\TestEvent $e)
    {
        $this->transaction->rollBack();
    }
}

tests/integration.suite.yml

# Codeception Test Suite Configuration
#
# Suite for unit or integration tests.

actor: UnitTester
extensions:
  enabled: [TransactionExtension]
modules:
    enabled:
        - \craft\test\Craft
        - Asserts
        - \Helper\Unit
myleshyson commented 3 years ago

Ok new update. Seems like this is happening because the Connection::EVENT_AFTER_OPEN is never getting triggered, so transactions don't happen in tests because that's what the Yii2 codeception module relies on. And it seems like that event would only ever get called if there's not a PDO connection already established.

The above workaround was actually interfering with fixtures as well, so what I ended up doing to get everything (fixtures, projectConfig, transactions) working together is manually triggering a Connection::EVENT_AFTER_OPEN event before each test, which ensured the yii2 modules picked up connections properly and thus rolled back properly. So new workaround currently looks like:

tests/_support/Helper/MyModule.php

<?php

namespace Helper;

use Codeception\TestInterface;
use craft\test\Craft;
use yii\db\Connection;

class MyModule extends Craft
{
    public function _before(TestInterface $test)
    {
        parent::_before($test);
        \Craft::$app->getDb()->trigger(Connection::EVENT_AFTER_OPEN);

    }
}

codeception.yml

actor: Tester
paths:
  tests: tests
  log: tests/_output
  data: tests/_data
  support: tests/_support
  envs: tests/_envs
bootstrap: _bootstrap.php
params:
  - tests/.env
modules:
  config:
    \Helper\MyModule:
      configFile: "tests/_craft/config/test.php"
      entryUrl: "http://host.docker.internal/index.php"
      projectConfig: {
        folder: "config/project",
        reset: false
      }
      migrations: []
      plugins: []
      cleanup: true
      transaction: true
      dbSetup: { clean: true, setupCraft: true }
angrybrad commented 3 years ago

Thanks for that... there probably is some wonkiness here we'll have to look into, either in Yii2's codeception module or how we're extending it.

myleshyson commented 11 months ago

I'm gonna close this. For anybody else finding themselves here I ended up just replacing codeception entirely with craft-pest. Way simpler to setup and transactions work great.

myleshyson commented 4 days ago

Ok looping back to this. Pest is fantastic, but it's also missing some great bells and whistles that Codception comes with. Just opened a pull request to actually try and address the underlying issue.