Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-Increment option #45

Open
soullivaneuh opened this issue Oct 23, 2017 · 9 comments
Open

Auto-Increment option #45

soullivaneuh opened this issue Oct 23, 2017 · 9 comments

Comments

@soullivaneuh
Copy link
Contributor

It was already asked on #29 but closed by the author.

I think it might be very interesting to propose it as an option, if this is technically possible.

Assert can not currently be done on IDs.

Test case example:

public function testUserIsLoadedFromGithub()
{
    $this->userManager->updateUser(
        (new User())
            ->setGithubId(1337)
            ->setUsername('toto')
            ->setPlainPassword(uniqid())
            ->setEmail('toto@toto.com')
    );
    $this->userManager->updateUser(
        (new User())
            ->setGithubId(4242)
            ->setUsername('uCustom')
            ->setPlainPassword(uniqid())
            ->setEmail('bar@foo.cc')
    );

    $user = $this->userProvider->loadUserByOAuthUserResponse(
        $this->createUserResponseMock(
            $this->container->get('hwi_oauth.resource_owner.github')
        )
    );
    static::assertSame(2, $user->getId());
    static::assertSame('uCustom', $user->getUsername());
    $this->assertUserAttributes($user, 'github');
}

Regards.

@dmaicher
Copy link
Owner

dmaicher commented Oct 24, 2017

If I understand the test correctly: Can you not do this?

$user = (new User())
            ->setGithubId(4242)
            ->setUsername('uCustom')
            ->setPlainPassword(uniqid())
            ->setEmail('bar@foo.cc')

$this->userManager->updateUser($user);

...

$loadedUser = $this->userProvider->loadUserByOAuthUserResponse(
    $this->createUserResponseMock(
        $this->container->get('hwi_oauth.resource_owner.github')
    )
);

static::assertSame($user->getId(), $loadedUser->getId());

I mean its hardly required to rely on hardcoded auto generated ID's in tests I believe.

@soullivaneuh
Copy link
Contributor Author

I can do this, indeed. :-)

But still, the option can be interesting if it's technically possible without pain. 😉

@aaa2000
Copy link

aaa2000 commented Nov 23, 2017

With postgresql and sequence strategy, this code in a custom WebTestCase seems to work

   /**
     * @before
     */
    protected function resetDatabaseSequences()
    {
        /** @var \Doctrine\ORM\EntityManager $em */
        $em = $this->getContainer()->get('doctrine')->getManager();
        /** @var \Doctrine\ORM\Mapping\ClassMetadata[] $metadatas */
        $metadatas = $em->getMetadataFactory()->getAllMetadata();
        foreach ($metadatas as $metadata) {
            if ($metadata->isIdGeneratorSequence()) {
                $em->getConnection()->executeQuery(sprintf(
                    'ALTER SEQUENCE %s RESTART',
                    $metadata->getSequenceName($em->getConnection()->getDatabasePlatform())
                ));
            }
        }
    }

@mnapoli
Copy link
Contributor

mnapoli commented Nov 19, 2018

Unfortunately with MySQL the same trick does not seem to be possible as ALTER TABLE triggers an implicit commit, which doesn't work with this package and triggers an error (#58).

If anyone has a trick to have predictable IDs I'm interested :)

@mnapoli
Copy link
Contributor

mnapoli commented Apr 5, 2019

A little update here: my workaround is to have a SQL script that resets the autoincrements.

Then this is what I do in my BeforeScenario in Behat:

    /**
     * @BeforeScenario
     */
    public function beforeScenario()
    {
        $defaultConnection = $this->doctrine->getConnection();
        // Reset the autoincrements
        $defaultConnection->exec(file_get_contents(__DIR__ . '/../../database/autoincrements.sql'));

        // The StaticDriver starts a transaction at the beginning, let's start from scratch because of the import above
        try {
            StaticDriver::commit();
        } catch (\Exception $e) {
            // There is a transaction only the first time
        }
        StaticDriver::beginTransaction();
    }

You'll notice the commit and try/catch: this is because on connection the StaticDriver auto-starts a transaction. That transaction is broken by the ALTER TABLE in autoincrements.sql, the only way I could make it work reliably was with this. Hope this helps because it took a lot of time to figure out ^^!

@dmaicher
Copy link
Owner

dmaicher commented Apr 9, 2019

@mnapoli actually very similar to a workaround I have in some tests 🙈

For me there is no need to catch any exceptions though:

    public function setUp(): void
    {
        parent::setUp();
        StaticDriver::rollBack();
        $this->em->getConnection()->executeQuery('ALTER TABLE foo AUTO_INCREMENT = 1000');
        StaticDriver::beginTransaction();
    }

@thewholelifetolearn
Copy link

I used @aaa2000 solution and tweaked it a bit so that it works with fixtures too:

    public static function tearDownAfterClass() : void
    {
        static::createClient();
        /** @var $em EntityManager */
        $em = self::$container->get('doctrine.orm.default_entity_manager');
        $metadatas = $em->getMetadataFactory()->getAllMetadata();
        foreach ($metadatas as $metadata) {
            if ($metadata->isIdGeneratorSequence()) {
                $max = $em->getConnection()->fetchAll(sprintf(
                    'SELECT MAX(%s) FROM %s',
                    $metadata->getSingleIdentifierColumnName(),
                    $metadata->getTableName()
                    )
                );
                $max = (is_null($max[0]['max']) ? 1 : $max[0]['max'] + 1);
                $em->getConnection()->executeQuery(sprintf(
                    'ALTER SEQUENCE %s RESTART WITH %d',
                    $metadata->getSequenceName($em->getConnection()->getDatabasePlatform()),
                    $max
                    )
                );
            }
        }
    }

It works with PosstgreSQL 10.3.

@desmax
Copy link

desmax commented Dec 21, 2019

You should not implement this :)
My tests got better just because I couldn't rely on ids.

@mislavjakopovic
Copy link

mislavjakopovic commented Mar 29, 2023

+1 for implementing this.

Yes on new project you should write tests from the start properly, but when you work on a company project which has >1500 tests that heavily rely on ids, refactoring all of those in favor of using this bundle is simply not an option.

Anyways, here's a quite performant and dynamic workaround which might help someone in future (inspired by knowledge from above answers):

protected function setUp(): void
{
    $connection = $this->getContainer()->get('doctrine.dbal.default_connection');

    $results = $connection->executeQuery(
        'SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() AND AUTO_INCREMENT > 1'
    )->fetchFirstColumn();

    foreach ($results as $result) {
        $connection->executeStatement('ALTER TABLE `' . $result . '` AUTO_INCREMENT = 1');
    }

    // The StaticDriver starts a transaction at the beginning, let's start from scratch because of the import above
    try {
        StaticDriver::commit();
    } catch (\Exception $e) {
        // There is a transaction only the first time
    }

    StaticDriver::beginTransaction();

    parent::setUp();
}

Before every test it will retrieve a list of tables which have been modified in last test (have AUTO_INCREMENT > 1) and issue reset just for those tables.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants