3

I am trying to figure out how to connect to multiple databases using Codeception. I have even tried directly instantiating a new PDO instance but then the codept run command just ends prematurely with no error message.

I can connect fine to one database using the Db module to use functions such as $I->seeInDatabase() but am stuck trying to connect to two. Trying to reconfigure the Db module at runtime with $this->getModule('Db')->_reconfigure() also just stops the tests with no message.

Any ideas?

Gabriel
  • 351
  • 2
  • 7
  • 13
  • Do you need the two connections at the same time? If not, you can configure different environments with different database connection parameters. – rickroyce May 29 '15 at 12:00
  • No, I mean I can even close one connection and change it to the other connection but I want to have the two connections in the same test, if that's what you're asking? – Gabriel May 29 '15 at 13:44

4 Answers4

5

I was able to achieve this by the following:

Suite configuration file:

actor: API_Tester
lint: true
colors: true
modules:
    enabled:
        - \Helper\Api
        - Asserts
        - REST:
            url: http://localhost
            depends: PhpBrowser
            part: Json
            headers:
              Accept: application/json
              Content-Type: application/json
        - Db:
            dsn: 'mysql:host=localhost;dbname=DATABASE1'
            user: 'DBUSER'
            password: 'DBPASSWORD'
            dump: 'tests/populate-DATABASE1.sql'
            populate: true
            cleanup: false
            reconnect: true
            populator: 'mysql -u $user -p$password -h $host $dbname < $dump'
            databases:
              DATABASE2_USE_THIS_NAME_IN_YOUR_CODE:
                dsn: 'mysql:host=localhost;dbname=DATABASE2'
                user: 'DBUSER'
                password: 'DBPASSWORD'
                dump: 'tests/populate-DATABASE2.sql'
                populate: true
                cleanup: false
                reconnect: true
                populator: 'mysql -u $user -p$password -h $host $dbname < $dump'

PHP test code:

        //Connect to the second database
        $I -> amConnectedToDatabase ( 'DATABASE2_USE_THIS_NAME_IN_YOUR_CODE' );
        $I -> seeNumRecords ( 1, 'users' );
        $I -> seeInDatabase ( 'users', [ 'email' => 'john@example.com' ] );
        //Connect back to the default database
        $I -> amConnectedToDatabase ( Codeception\Module\Db::DEFAULT_DATABASE );
        $I -> seeNumRecords ( 1, 'sessions' );
        $I -> seeInDatabase ( 'sessions', [ 'uniqueness' => $this -> uuid ] );

Result:

I am connected to database "DATABASE2_USE_THIS_NAME_IN_YOUR_CODE"
I see num records 1,"users"
I see in database "users",{"email":"john@example.com"}
I am connected to database "default"
I see num records 1,"sessions"
I see in database "sessions",{"uniqueness":"635c798c-05cd-4d9d-b79a-9643...}
András Szabácsik
  • 1,957
  • 1
  • 16
  • 11
  • I upvoted this answer for indicating how to refer to the default configured database (``\Codeception\Module\Db::DEFAULT_DATABASE``) which is not currently mentioned in the official documentation. https://codeception.com/docs/modules/Db#Example-with-multi-databases – rijam Nov 18 '19 at 10:53
2

I think this is what you want: https://github.com/Codeception/Codeception/issues/1634 ... however, its not implemented yet - maybe you can push this issue?

Marc
  • 21
  • 2
  • Welcome to Stackoverflow! Try to avoid link only answers. There should be enough information to provide an answer by itself and a link can be provided for more information. Also, if your link breaks, your answer as it is becomes useless. We hope you stick around and help improve our community! – CJ Dennis Jun 03 '15 at 15:27
  • Hi Dennis, thanks for your advice! I try to improve my informations. What I wanted to say is, there is an open ticket for this issue but its still pending. Perhaps we can push this issue if we are enough interested participants. – Marc Jun 04 '15 at 08:44
  • To contribute even if it is just a single link: https://github.com/natterbox/Codeception-MultiDb – velop Nov 19 '15 at 20:49
  • I do not think, that a link to a feature request at the official website is not a good answer. Even if it's a liink-only. – Vladislav Rastrusny Mar 25 '16 at 08:16
0

Well! if you are not working with mock database connections ,you can do this thing which I implemented once in symfony while using codeception.

  1. Grab Doctrine service using

    $doctrine = $I->grabService('Doctrine').
    
  2. Now grab the Database connection as mentioned by you in doctrine ORM section.

    $connection = $doctrine->getConnection('DB_Name_as_in_ORM')
    
  3. You are now connected to the required Database and now you can execute Queries on table in this database using "doctrine dbal connection methods".

I think this is what you were looking for, maybe!

yunzen
  • 32,854
  • 11
  • 73
  • 106
0

I wanted to be able to use Doctrine multiple managers setup and all codeception methods with it but it kept breaking (didn't see set attributes, worked only on default manager etc.) and this is what I came up with: In a Helper I've created method for setting doctrine manager service using Symfony module:

class Shared extends \Codeception\Module
{
    public \Doctrine\ORM\EntityManagerInterface $em;

    /**
     * @param string $emAlias Name of your entity manager in doctrine.yaml
     */
    public function changeEm(string $emAlias): void
    {
        if ('internal' !== $connection && 'api' !== $emAlias) {
            throw new \Error('Unknown connection: ' . $emAlias);
        }

        $doctrine = $this->getService(ManagerRegistry::class);
        $em = $doctrine->getManager($emAlias);
        // We need to verify that connection is open as it gets shut down on error
        if (!$em->isOpen()) {
            $doctrine->resetManager($emAlias);
        }
        $this->setEm($em, $emAlias);
    }

    public function setEm(EntityManagerInterface $em, string $emAlias): void
    {
        $this->em = $em; // Just to be safe and have access to selected entity        
        $this->getModule('Doctrine2')->em = $em;

        $sfModule = $this->getModule('Symfony');
        /** @var \Symfony\Bundle\FrameworkBundle\Test\TestContainer $container */
        $container = $sfModule->_getContainer();

        $service = 'doctrine.orm.' . $emAlias . '_entity_manager';
        if ($container->initialized($service)) {
            return;
        }
        $container->set($service, $em);
        $sfModule->persistService($service, true);
    }
}

and with that always set default entity manager at the top of method shared by all your tests (which is, in my case, _before in tests parent class):

abstract class BaseCestAbstract
{
    public function _before(ApiTester $I): void
    {
        $I->changeEm('internal'/'api');
        [...]
    } 
}

And now if you want to change manager Codeception is using mid-test:

    public function testA(ApiTester $I): void
    {
        $I->changeEm('internal');
        [...]
        $I->changeEm('api');
        [...]
    } 

all methods like grabEntityFromRepository should be working and secondary booted Kernel for API calls should be using the same entity as you.

Drawbacks:

I couldn't figure out how to make it work with cleanup: true which is a pain, but I mainly use it for REST calls which don't really work with cleanup anyway, so it is acceptable in my case.

Versions:

  • Symfony: 6.2.6
  • Codeception: 5.0.8
  • codeception/module-doctrine2: 3.0.1
  • doctrine/doctrine-bundle: 2.8.3
Mortimer
  • 300
  • 5
  • 11