I'm writing tests in Symfony and using fixtures to load test data into the database. One of the entities I need to create is a user, which has a password field. In production, passwords are hashed using Argon2id before being stored in the database.
When defining the fixture, should I manually hash the password using the same algorithm, or can I store it as plain text since it's just for testing purposes? This encoder is defined in Yaml security
If it’s still necessary, am I doing it right? I have a problem when passing an encoder parameter inside a fixture located in an abstract class
Fixture
class AuthUserFixture extends Fixture
{
private UserPasswordEncoderInterface $encoder;
public function __construct(UserPasswordEncoderInterface $encoder) {}
/**
* @inheritDoc
*/
public function load(ObjectManager $manager): void {
$this->loadUsers($manager);
}
private function loadUsers(ObjectManager $manager): void
{
foreach ($this->getUserData() as [
$username, $usernameCanonical, $email, $emailCanonical, $enabled, $password, $roles
]) {
$user = new User();
$user->setUsername($username);
$user->setUsernameCanonical($usernameCanonical);
$user->setEmail($email);
$user->setEmailCanonical($emailCanonical);
$user->setEnabled($enabled);
$user->setRoles($roles);
$password = $this->encoder->encodePassword($user, $password);
$user->setPassword($password);
$manager->persist($user);
$this->addReference($username, $user);
}
$manager->flush();
}
private function getUserData(): array
{
// $userData = [$username, $usernameCanonical, $email, $emailCanonical, $enabled, $password, $roles];
return [
[
'User',
'user',
'[email protected]',
'[email protected]',
true,
'7jKuXn97ReDK',
[User::ADMIN_DASHBOARD_ACCESS_ROLES_HAYSTACK]
],
];
}
}
Abstract Class
/** @var KernelBrowser $client */
protected KernelBrowser $client;
/** @var EntityManagerInterface */
private $entityManager;
public static function setUpBeforeClass(): void
{
$kernel = static::createKernel();
$kernel->boot();
$entityManager = $kernel->getContainer()->get('doctrine')->getManager();
$loader = new Loader();
foreach (self::getFixtures() as $fixture) {
$loader->addFixture($fixture);
}
$purger = new ORMPurger();
$purger->setPurgeMode(ORMPurger::PURGE_MODE_DELETE);
$executor = new ORMExecutor($entityManager, $purger);
$executor->execute($loader->getFixtures());
}
protected function setUp(): void
{
parent::setUp();
$this->client = static::createClient();
$this->client->disableReboot();
$this->client->setServerParameter('HTTP_HOST', '172.19.0.1');
$this->entityManager = $this->client->getContainer()->get('doctrine.orm.entity_manager');
$this->entityManager->beginTransaction();
$this->entityManager->getConnection()->setAutoCommit(false);
}
protected function tearDown(): void
{
if ($this->entityManager->getConnection()->isTransactionActive()) {
try {
$this->entityManager->rollback();
} catch (\Exception $e) {
echo "Rollback error: " . $e->getMessage();
}
}
$this->entityManager->clear();
$this->entityManager->close();
parent::tearDown();
}
protected function apiLoginAs(string $username, string $password): void
{
$expectedBase64 = base64_encode("$username:$password");
$authHeader = 'Basic ' . $expectedBase64;
$this->client->setServerParameter('HTTP_Authorization', $authHeader);
}
private static function getFixtures(): iterable
{
return [
new AuthUserFixture(),
];
}
I don’t fully understand whether the implementation is correct. And how to correctly pass the parameter correctly to
private static function getFixtures(): iterable
{
return [
new AuthUserFixture($encoder),
];
}
Because when passing this parameter, an error is displayed to me
Notice: Undefined variable: encoder
How to initialize it? and do I even need a constructor in fixtures?
I'm writing tests in Symfony and using fixtures to load test data into the database. One of the entities I need to create is a user, which has a password field. In production, passwords are hashed using Argon2id before being stored in the database.
When defining the fixture, should I manually hash the password using the same algorithm, or can I store it as plain text since it's just for testing purposes? This encoder is defined in Yaml security
If it’s still necessary, am I doing it right? I have a problem when passing an encoder parameter inside a fixture located in an abstract class
Fixture
class AuthUserFixture extends Fixture
{
private UserPasswordEncoderInterface $encoder;
public function __construct(UserPasswordEncoderInterface $encoder) {}
/**
* @inheritDoc
*/
public function load(ObjectManager $manager): void {
$this->loadUsers($manager);
}
private function loadUsers(ObjectManager $manager): void
{
foreach ($this->getUserData() as [
$username, $usernameCanonical, $email, $emailCanonical, $enabled, $password, $roles
]) {
$user = new User();
$user->setUsername($username);
$user->setUsernameCanonical($usernameCanonical);
$user->setEmail($email);
$user->setEmailCanonical($emailCanonical);
$user->setEnabled($enabled);
$user->setRoles($roles);
$password = $this->encoder->encodePassword($user, $password);
$user->setPassword($password);
$manager->persist($user);
$this->addReference($username, $user);
}
$manager->flush();
}
private function getUserData(): array
{
// $userData = [$username, $usernameCanonical, $email, $emailCanonical, $enabled, $password, $roles];
return [
[
'User',
'user',
'[email protected]',
'[email protected]',
true,
'7jKuXn97ReDK',
[User::ADMIN_DASHBOARD_ACCESS_ROLES_HAYSTACK]
],
];
}
}
Abstract Class
/** @var KernelBrowser $client */
protected KernelBrowser $client;
/** @var EntityManagerInterface */
private $entityManager;
public static function setUpBeforeClass(): void
{
$kernel = static::createKernel();
$kernel->boot();
$entityManager = $kernel->getContainer()->get('doctrine')->getManager();
$loader = new Loader();
foreach (self::getFixtures() as $fixture) {
$loader->addFixture($fixture);
}
$purger = new ORMPurger();
$purger->setPurgeMode(ORMPurger::PURGE_MODE_DELETE);
$executor = new ORMExecutor($entityManager, $purger);
$executor->execute($loader->getFixtures());
}
protected function setUp(): void
{
parent::setUp();
$this->client = static::createClient();
$this->client->disableReboot();
$this->client->setServerParameter('HTTP_HOST', '172.19.0.1');
$this->entityManager = $this->client->getContainer()->get('doctrine.orm.entity_manager');
$this->entityManager->beginTransaction();
$this->entityManager->getConnection()->setAutoCommit(false);
}
protected function tearDown(): void
{
if ($this->entityManager->getConnection()->isTransactionActive()) {
try {
$this->entityManager->rollback();
} catch (\Exception $e) {
echo "Rollback error: " . $e->getMessage();
}
}
$this->entityManager->clear();
$this->entityManager->close();
parent::tearDown();
}
protected function apiLoginAs(string $username, string $password): void
{
$expectedBase64 = base64_encode("$username:$password");
$authHeader = 'Basic ' . $expectedBase64;
$this->client->setServerParameter('HTTP_Authorization', $authHeader);
}
private static function getFixtures(): iterable
{
return [
new AuthUserFixture(),
];
}
I don’t fully understand whether the implementation is correct. And how to correctly pass the parameter correctly to
private static function getFixtures(): iterable
{
return [
new AuthUserFixture($encoder),
];
}
Because when passing this parameter, an error is displayed to me
Notice: Undefined variable: encoder
How to initialize it? and do I even need a constructor in fixtures?
Share Improve this question edited Feb 6 at 14:38 AymDev 7,5544 gold badges33 silver badges57 bronze badges asked Feb 6 at 13:04 Єськіна Анна АндріївнаЄськіна Анна Андріївна 12 bronze badges New contributor Єськіна Анна Андріївна is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct. 7- Wouldn't storing the password in plain text make it impossible to use it properly? Why not store it in the same way as passwords of production users? – Nico Haase Commented Feb 6 at 13:12
- "An error also occurred during its initialization." - if you need help to resolve that error, please share it – Nico Haase Commented Feb 6 at 13:12
- Finally, wouldn't it be simpler to load all fixtures before running your tests, as in: completely independent? – Nico Haase Commented Feb 6 at 13:13
- ArgumentCountError : Too few arguments to function App\Tests\DataFixtures\AuthUserFixture::__construct(), 0 passed in /home/yeskina-a/amazon-admin/releases/1/tests/AbstractTestClass/AbstractAuthenticatedTest.php on line 80 and exactly 1 expected But when I pass a parameter to the fixture, I get "Undefined variable '$encoder' ". How can I initialize correctly? or shouldn’t create a constructor at all – Єськіна Анна Андріївна Commented Feb 6 at 13:18
- Please add all clarification to your question by editing it – Nico Haase Commented Feb 6 at 13:28
1 Answer
Reset to default 0You should not mix your tests with data fixtures. Here the issue is that you instanciate the fixture class yourself in the getFixtures()
method of your test class. This won't work as it needs the UserPasswordEncoderInterface
service: you don't want to build it yourself and the most important part, you want to get the correct service (according to your configuration in security.yaml).
Plaintext passwords
Symfony allows to disabled password hashing for testing purposes. You are right that it is not necessary, and it will make your test suite run faster. You can find an example in the documentation. Here's what I'd suggest:
# apply this config only for the `test` environment
when@test:
security:
password_hashers:
# replace with the correct FQCN for your `User` class
'Your\UserClass\Fqcn':
algorithm: plaintext
Testing dataset
It looks like you want to have a fixed dataset for every test you run. The DAMADoctrineTestBundle is built for that purpose and recommended in the Symfony documentation: install it and enable the PHPUnit extension.
As recommended (but you may easily miss that info), you can create a test bootstrapping file to automatically recreate the database and load your fixtures. First create the file (let's say tests/database.php):
<?php
use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
$kernel = new Kernel('test', true);
$kernel->boot();
$application = new Application($kernel);
$application->setAutoExit(false);
// Deletes the testing database
$application->run(new ArrayInput([
'command' => 'doctrine:database:drop',
'--if-exists' => 1,
'--force' => 1,
]));
// Creates the testing database
$application->run(new ArrayInput([
'command' => 'doctrine:database:create',
]));
// Runs your Doctrine migrations
$application->run(new ArrayInput([
'command' => 'doctrine:migrations:migrate',
'--no-interaction' => 1,
]));
// Loads your fixtures
$application->run(new ArrayInput([
'command' => 'doctrine:fixtures:load',
'--no-interaction' => 1,
]));
Then include this file in the main PHPUnit bootstrap file tests/bootstrap.php:
// ...
// Database bootstrapping
require_once __DIR__ . '/database.php';
Separate dev
/test
databases
Make sure you have a specific database name for testing so it won't touch your development database in any way. You have 2 options:
set a database name suffix in the Doctrine configuration (should be enabled by default):
# in doctrine.yaml when@test: doctrine: dbal: # "TEST_TOKEN" is typically set by ParaTest dbname_suffix: '_test%env(default::TEST_TOKEN)%'
set a custom database name in the PHPUnit configuration:
<!-- in phpunit.xml.dist --> <phpunit ... > <php> <!-- ... --> <server name="DATABASE_URL" value="<!-- SET DESIRED VALUE HERE -->" /> </php> <!-- ... -->