The test does not see the data from the fixture

37 Views Asked by At

I have a fixture to fill in the test user

namespace Tests\Fixture;

use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
use App\Entity\User;

final class UserFixture extends Fixture
{
    public function load(ObjectManager $manager): void
    {
        $userNonAdmin = new User();
        $userNonAdmin->setUsername('uuuuusername');
        $userNonAdmin->setPassword('$2y$10$qk4meHxphpa4qXef6QHC4uNQN/rFsa.iKWdS/8yB.bER1tdkSR6nS'); // hashed 123456
        $userNonAdmin->setEmail('[email protected]');
        $userNonAdmin->setRoles(['ROLE_USER']);
        $manager->persist($userNonAdmin);

        $manager->flush();
    }
}

And a test that uses this fixture

namespace Tests\Acceptance\User;

use Tests\Fixture\UserFixture;
use Tests\Support\AcceptanceTester;

final class UserAuthCest
{
    private const ENDPOINT = '/user/auth';

    public function _before(AcceptanceTester $I): void
    {
        $I->loadFixtures([UserFixture::class]);
    }

    public function positiveTest(AcceptanceTester $I): void
    {
        $data = [
            'username' => 'uuuuusername',
            'password' => '123456'
        ];

        $I->sendPost(self::ENDPOINT, $data);

        $response = $I->grabResponse();

        dd(json_decode($response, true));
    }
}

But as a result, I get the error that the user has not been found, although if I get a UserRepository in the test and try to find the user using this username, then it finds the entity

public function positiveTest(AcceptanceTester $I): void
{
    $data = [
        'username' => 'uuuuusername',
        'password' => '123456'
    ];

    $I->sendPost(self::ENDPOINT, $data);

    /** @var UserRepository $repository */
    $repository = $I->grabRepository(UserRepository::class);
    $user = $repository->findOneBy(['username' => 'uuuuusername']); // Entity exists
    
    $response = $I->grabResponse();

    dd(json_decode($response, true));
}

What am I doing wrong?

Symfony version: 6.3

Codeception version: 5.1.0

2

There are 2 best solutions below

0
Angus123 On BEST ANSWER

I found what the problem was, I had to fix the tests/Acceptance.suite.yml file

actor: AcceptanceTester
modules:
    enabled:
        - Doctrine2:
            depends: Symfony
            cleanup: false // <-- replace true with false

And create an extension to automatically clean up your test database

final class DatabaseMigrationExtension extends Extension
{
    public static array $events = [
        Events::SUITE_BEFORE => 'beforeSuite',
        Events::SUITE_AFTER => 'afterSuite',
    ];

    public function beforeSuite(): void
    {
        try {
            /** @var Cli $cli */
            $cli = $this->getModule('Cli');
            /** @var Symfony $symfony */
            $symfony = $this->getModule('Symfony');

            $symfony->_getEntityManager()->getConnection()->close();

            $this->writeln('Recreating the database...');
            $cli->runShellCommand('bin/console doctrine:database:drop --if-exists --force');
            $cli->seeResultCodeIs(0);
            $cli->runShellCommand('bin/console doctrine:database:create');
            $cli->seeResultCodeIs(0);

            $this->writeln('We are launching migrations...');
            $cli->runShellCommand('bin/console doctrine:migrations:migrate --no-interaction');
            $cli->seeResultCodeIs(0);

            $this->writeln('Loading textures...');
            $cli->runShellCommand('php bin/console doctrine:fixtures:load --append --group=test');
            $cli->seeResultCodeIs(0);

            $this->writeln('The test base has been created!');
        } catch (Throwable $e) {
            $this->writeln(
                sprintf(
                    'Error beforeSuite: %s',
                    $e->getMessage()
                )
            );
        }
    }

    public function afterSuite(): void
    {
        try {
            /** @var Cli $cli */
            $cli = $this->getModule('Cli');

            $cli->runShellCommand('bin/console doctrine:database:drop --if-exists --force');
            $cli->seeResultCodeIs(0);
            $cli->runShellCommand('bin/console doctrine:database:create');
            $cli->seeResultCodeIs(0);

            $this->writeln('The test base has been cleared!');
        } catch (Throwable $e) {
            $this->writeln(
                sprintf(
                    'Error afterSuite: %s',
                    $e->getMessage()
                )
            );
        }
    }
}
0
Peter On

There are a few interesting points in the above issue and solution:

  1. You are calling Acceptance test, which means that PhpBrowser module should be used and a call will go through browser, and not through Symfony core
  2. Doctrine2 -> cleanup: true setting means that all actions inside a test method will be executed inside a transaction and will be cleaned up at the end of test.

The conclusion from the above points is - when cleanup is enabled, the fixtures will be loaded in a transaction that will not be available during the browser call and the app will return failure response. If cleanup is disabled the surrounding transaction is missing for fixture loading and the data will be available when endpoint is called through browser.

The downside of the solution is - the fixtures will be loaded and saved multiple times on each test run, which can cause a side effects depending on how much times test was executed or a test failure because of unique key constraint.

A few suggestions:

  1. The provided test looks more like a Functional, not Acceptance, so it would be easier to move the test to Functional type. In this case the same code will work with enabled Doctrine2 cleanup setting
  2. Please consider the following info about cleanup setting from Codeception docs: "You cannot use cleanup: true in an acceptance test, since Codeception and your app (i.e. browser) are using two different connections to the database, so Codeception can’t wrap changes made by the app into a transaction."