Reset database at the end of test - symfony

I want to reset my test database after each test but somehow can't find a way to do it. Can delete the data added in each test but I am pretty sure this is not the correct approach. What I have as test is simply this:
<?php
declare(strict_types=1);
namespace Tests\Unit\Entity;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Tests\Shared\Factory\UserFactory;
final class UserTest extends KernelTestCase
{
public function testGettersReturnCorrectData(): void
{
self::bootKernel();
/** #var UserRepository $repo */
$repo = self::getContainer()->get(UserRepository::class);
$user = UserFactory::createUser();
$repo->add($user, true);
$this->assertSame($user->getEmail(), UserFactory::EMAIL);
$this->assertSame($user->getPassword(), UserFactory::PASSWORD);
$this->assertSame($user->getReferralCode(), UserFactory::REFERRAL_CODE);
}
}
The newly created user stays in the db. I am using MySQL test db because my production db will be MySQL and don't want to reach some corner cases because of the different dbs.
This is my env.test if needed.
# define your env variables for the test env here
KERNEL_CLASS='App\Kernel'
APP_SECRET='$ecretf0rt3st'
SYMFONY_DEPRECATIONS_HELPER=999999
PANTHER_APP_ENV=panther
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
DATABASE_URL="mysql://root:toma123#127.0.0.1:3306/api?serverVersion=5.7&charset=utf8mb4"

by the love of god, follow #craigh 's advice and look into foundry
https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#database-reset
they do exactly that, and actually provide good stuff so you can build your test database inside each test case. Symfony's fixtures solution, in my subjective opinion, is worse than foundry.

Related

excluding tables with setExcludedDoctrineTables when loading fixtures with liip TestFixturesBundle

I'm trying to use Liip test bundle to load fixtures from code using the loadFixtures() method from FixturesTrait
However, I need to exclude the RESOURCE table that I don't want to be dropped in the process. If I understand correctly, this should be easy using the setExcludedDoctrineTables method according to the doc https://github.com/liip/LiipTestFixturesBundle/blob/master/doc/database.md
Unfortunately, when I run this code, the table RESOURCES gets dropped with all the others.
Can anybody see why ?
not sure if that's relevant but I'm using a mysql db in a separate docker container.
<?php
namespace App\Tests\Controller;
use Symfony\Component\Panther\PantherTestCase;
use Symfony\Component\Panther\Client;
use Facebook\WebDriver\WebDriverBy as By;
use Facebook\WebDriver\Exception\TimeoutException;
use Liip\FunctionalTestBundle\Test\WebTestCase;
use Symfony\Component\Panther\PantherTestCaseTrait;
use Liip\TestFixturesBundle\Test\FixturesTrait;
use App\Repository\UserRepository;
use App\DataFixtures\UserFixtures;
use App\DataFixtures\AccountFixtures;
abstract class AbstractPantherTest extends WebTestCase{
// use trait so we can combine Liip and Panther features
use PantherTestCaseTrait; // this is the magic. Panther is now available.
// provide fixtures loading feature
use FixturesTrait;
// #var Symfony\Component\Panther\Client
protected static $client;
//Initialize the test case
function setUp():void
{
static::bootKernel();
if(self::$client === null){
self::$client = self::createPantherClient(['browser' => PantherTestCase::FIREFOX]);
$this->setExcludedDoctrineTables(["RESOURCES"]);
$this->loadFixtures([
UserFixtures::class,
]);
// retrieve the test user
$userRepository = static::$container->get(UserRepository::class);
// retrieve the test user
$testUser = $userRepository->findOneByUsername('Administrator');
// simulate $testUser being logged in
self::doLogin($testUser->getUsername(), 'xxx');
}
}
}
Answering my own question as I just found out the issue. Sometimes a good night of sleep is all you need :)
So this code is actually working. The table RESOURCES got dropped because the setUp method was redefined in the concrete class implementing the test and that redefinition included another call to loadFixtures, but for a different data class and without the exclusion :
<?php
namespace App\Tests\Controller;
use App\DataFixtures\DiscountFixtures;
class DiscountControllerWebTest extends AbstractPantherTest
{
function setUp():void
{
parent::setUp();
$this->loadFixtures([
DiscountFixtures::class,
]);
}

Add data when running Symfony migrations

I have a Symfony project that is using the DoctrineMigrations bundle, and I have a really simple question: When I run a migration (e.g., when I'm pushing an update to production), how can I insert data to the database?
For example: I have an Entity which is the type of an add. The entity is:
private $addType; // String
private $type1; // Boolean
private $type2; // Boolean
private $type3; // Boolean
I add another field ($type4), and I want to add a new record to the database, with this values:
$addType = 'Type number 4';
$type1 = false;
$type2 = false;
$type3 = false;
$type4 = true;
How can this be done with DoctrineMigrations? Is it possible?
Using the Entity Manager as suggested in another answer is not a good idea, as it leads to troubles later.
In the first migration, I created a table with users and populated some users via $em->persist($user); which seemed fine at the beginning.
But after a month, I added a phone column to my User model. And Doctrine generates INSERT statements with this column within the first migration, which fails due to the non-existing column phone. Of course it doesn't exist yet in the first migration. So it is better to go with pure SQL INSERTs.
I just asked a related related question.
It is possible to use the migrations bundle to add data to the database. If you add a new property and use the doctrine mapping then the
php app/console doctrine:migrations:diff
command will generate a new migration file. You can just put your insert statements inside this file using the syntax:
$this->addSql('INSERT INTO your_table (name) VALUES ("foo")');
Make sure you put it after the auto-generated schema changes though. If you want to separate your schema changes and your data changes then you can use
php app/console doctrine:migrations:generate
to create an empty migrations file to put your insert statements in.
Like I said in my related question, this is one way to do it, but it requires manually creating these if you want to change this data in the database.
Edit:
Since this answer seems to get a few views I think it's worth adding that to more clearly separate the data changes from the schema changes there is a postUp method that can be overridden and that will be called after the up method.
https://www.doctrine-project.org/projects/doctrine-migrations/en/3.0/reference/migration-classes.html#postup
I've "found" the correct way to solve my problem (insert data after running migrations, using my entity classes).
Here is: https://stackoverflow.com/a/25960400
The idea is to declare the migration as ContainerAware, and then, from the postUp function, call the DI to get the EntityManager. It's really easy, and you can use all your entities and repositories.
// ...
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class Version20130326212938 extends AbstractMigration implements ContainerAwareInterface
{
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function up(Schema $schema)
{
// ... migration content
}
public function postUp(Schema $schema)
{
$em = $this->container->get('doctrine.orm.entity_manager');
// ... update the entities
}
}
when you make the new field you need to enter this annotation "options={"default":1}" and it should work.
/**
* #var boolean
* #ORM\Column(name="type4", type="boolean", options={"default":1})
*/
private $type4 = true;
Took me some time to figure this out :)
It does, if you know how to format the array;
$this->connection->insert('user', ['id' => 1, 'gender' => 'Male']);
this is good solution for me. Just use bin/console make:migration and when migration is generated just edit if and add "DEFAULT TRUE":
$this->addSql('ALTER TABLE event ADD active TINYINT(1) NOT NULL DEFAULT TRUE');
It doesn't sound a good idea to fill date in migration, not its responsibility, symfony has a way of doing that. https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html

Test database isolation with Behat 3, Doctrine 2 and Symfony 2

I am introducing functional behat tests on a Symfony2/Doctrine2 application and am deciding how to handle database isolation and data fixtures.
Are there any pitfalls to setting up a separate test environment with its own completely separate mysql database that gets populated by a dump import prior to execution of the behat test suite, and then emptied after suite execution? I'm trying to avoid using data fixtures unless I really need so as to save the time of manually writing foreign key relations and what not.
Any guidance is appreciated.
As far as Symfony/Doctrine/Behat is concerned and if you want to be one of those who follows the best practises then:
You should isolate your environment dev, test, prod, stag ...
Setting up isolated environments as a symfony application base
up to Build folder structure header.
You should isolate your test database and use sqlite instead of
MySQL for performance purposes. Using multiple SQLite entity
managers for multiple bundles and databases in test environment,
you can use only one.
You should use fixtures to give yourself flexibility and get rid of
burden of manual processing. Do not try to avoid using them!
Creating doctrine data fixtures in symfony
So on ..... just check the posts in this site which I often
read up on myself.
Behat 3 composer entries and the behat.yml
We currently have a separate test database and use a combination of both fixtures and prepopulated database.
The prepopulated database contains the minimum information that needs to be present in almost all tests. We used to do this with fixtures but it was too slow, so now we populate the DB like so:
/**
* #BeforeScenario
*/
function initialiseStorage(BeforeScenarioScope $scope)
{
$con = $this->getService('database_connection');
$con->executeUpdate("SET foreign_key_checks = 0;");
$filePath = $this->getMinkParameter('files_path').'/test_db.sql';
$con->exec(file_get_contents($filePath));
$con->executeUpdate("SET foreign_key_checks = 1;");
}
And then, we load the specific fixtures for every test case like Alfonso described.
We use MYSQL for our tests as in our experience the bottleneck is not the DB but doctrine's metadata caching. If you set up metadata caching in redis the speed of the tests increase dramatically.
In order to respond to #madness-method and to complete the following answer:
The prepopulated database contains the minimum information that needs
to be present in almost all tests. We used to do this with fixtures
but it was too slow, so now we populate the DB like so:
/**
* #BeforeScenario
*/
function initialiseStorage(BeforeScenarioScope $scope)
{
$con = $this->getService('database_connection');
$con->executeUpdate("SET foreign_key_checks = 0;");
$filePath = $this->getMinkParameter('files_path').'/test_db.sql';
$con->exec(file_get_contents($filePath));
$con->executeUpdate("SET foreign_key_checks = 1;");
}
And then, we load the specific fixtures for every test case like
Alfonso described.
We use MYSQL for our tests as in our experience the bottleneck is not
the DB but doctrine's metadata caching. If you set up metadata caching
in redis the speed of the tests increase dramatically.
You should use instead:
/**
* #BeforeScenario
*/
function initialiseStorage(BeforeScenarioScope $scope)
{
$con = $this->em->getConnection();
$con->executeUpdate("SET foreign_key_checks = 0;");
$filePath = $this->getMinkParameter('files_path').'/test_db.sql';
$con->exec(file_get_contents($filePath));
$con->executeUpdate("SET foreign_key_checks = 1;");
}
having the following code in your Context file:
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
Do not forget to add the following lines in your behat configuration file regarding your contexts in order to be able to use the entity manager in your constructor and then in your initialiseStorage method :
- AppBundle\Features\Context\FeatureContext:
em: '#doctrine.orm.default_entity_manager'
Basically to get the connection, we have replaced:
$this->getService('database_connection');
by:
$this->em->getConnection();
I recommend you to read something about factory girl pattern. The idea is create a
factory for every class that you have and use a instance of it in the test. I use https://github.com/carlescliment/handy-tests-bundle
Other options will be create your own steps to create a instance or the class something like this:
/**
* #Given /^there are products:$/
*/
public function thereAreRoutes(TableNode $table)
{
$em = $this->getEntityManager();
foreach ($table->getHash() as $hash) {
$entity = new Product();
$entity->setName($hash['name']);
$entity->setDescription(isset($hash['description']) ? $hash['description'] : $hash['description']);
$em->persist($entity);
}
$em->flush();
}
And you can use it like:
Given there are products:
| name | description |
| Shoes | It is blue |

Symfony 2: Static function [JMSTranslation]

I'm currently working on a project that the previous developer integrated with JMSTranslationBundle.
At this moment, I've did some modification to the application, one of them was to turn the menu to be highly dynamic. (Basically, the user logic of the application have 3 layers and each layer have their own menu).
The menu are stored into the database and accessible through the doctrine entity. To display the label, I store into the DB the "label code" which is used by JMSTranslationBundle as a key to identify it. The desc is by default empty until setted into the translation file. (editable with the _trans route).
Into the documentation of JMS, it is mentionned that one can implement TranslationContainerInterface so when the compilation of the translation file (who are XLIFF file currently) are done, each class implementing this will be called to return a list of Message objects. Here's my issue:
The function to implement is static, meaning that when call, my model Menu (who handle to logic of fetching throught Doctrine repo) is not loaded via the service manager. This means that I do not receive the repository object (since it's loaded by service and pass through the controller):
public function __construct(MenuRepository $objMenuRepo)...
The definition of the function I implements is:
static function getTranslationMessages(){ ... }
My question is: how can I obtain the doctrine (either manager or repository) within that static function . (Since this will be only called on translation initial generation and not by the site itsef, performance is not an issue I worry about).
Also: If anyone have better alternative to propose (that wouldn't involved getting rid of this translation bundle, trust me, it would take quite an amount of time right now), I'm opened to hear them.
Thank you :-)
If some of you are interested, I had to use an alternative solution.
Although it doesn't answer the question on how to use a service within a static context, it will help those who ran into the same issue I had when attempting to implement with JMSTranslation.
To implement the solution (to extract translation key from the database), I had to use the JMS\TranslationBundle\Translation\ExtractorInterface.
I have implement it under this format:
class TranslationRepositoriesExtractor implements ExtractorInterface{
//Loaded through the service container
public function __construct(EntityRepository $objRepositoryNeeded);
// Implementation of the interface ExtractorInterface.
// Within this function, I've used the EntityRepository received in the
// constructor to fetch the list of keys that would be use for translating
/**
* #return \JMS\TranslationBundle\Model\Message[]
*/
public function extract()
}
As you can notice, the extract function return an array of \JMS\TranslationBundle\Model\Message.
After implementing this function, you have to add your object as a service and make it recognizable by JMSTranslationBundle as an extractor. To do so:
<!-- Replace the id of the service, the class path, the id of the argument and the alias
named you want by the value you need in your application -->
<service id="idOrYourService" class="Path\Of\Class\TranslationRepositoriesExtractor">
<argument type="service" id="repository.needed" />
<tag name="jms_translation.extractor" alias="NameOfAlias" />
</service>
The alias tag is used within JMSTranslationBundle to recognize your class as an extractor.
Finally, when generating the files, I had to had to enable the extractor. This can be done via the config, but in my case, was done manually through the command line
php app/console translation:extract --enable-extractor=NameOfAlias en
// NameOfAlias is the same name as the one defined in the tag of your service
I hope I didn't forget any step (if so, feel free to reply in a comment and I'll update the answer).
Happy coding :-)
Using this input, I ended up coding this version of the extractor.
<?php
namespace MyBundle\Service;
use Doctrine\ORM\EntityManager;
use JMS\TranslationBundle\Model\Message;
use JMS\TranslationBundle\Model\MessageCatalogue;
use JMS\TranslationBundle\Translation\ExtractorInterface;
/**
* Extracts translatable strings from Doctrine entities
*
* #package MyBundle\Service
*/
class EntityTranslationExtractor implements ExtractorInterface
{
/**
* #var EntityManager
*/
private $entityManager;
/**
* EntityTranslationExtractor constructor.
*
* #param EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* #return MessageCatalogue
*/
public function extract()
{
$messageCatalogue = new MessageCatalogue();
// Sample portion of the extraction
$translatableEntities = $this->entityManager->getRepository('MyBundle:MyEntity')->findAll();
foreach ($translatableEntities as $entity) {
$message = new Message($entity::class .'.'. $entity->getName(). '.name');
$message->setDesc(ucwords($entity->getName()));
$messageCatalogue->add($message);
}
return $messageCatalogue;
}
}

Can i use getDoctrine and getManager in an entity? I'm sorry but i don't understand how this works

I want to use the getDoctrine and getManager functions in an entity. Is this possible? or is there any way arround this? I want to insert something in a database like this :
$history = new Policy();
$history->setName($file1->getClientOriginalName());
$history->setPolicyNumber($this->getPolicyNumber());
$history->setOrderId($this->getOrderId());
$history->setPath($this->getPathFile1());
$history->setDocumentType($this->getDocument1Type());
$history->setPrintAction($this);
$em = $this->getDoctrine()->getManager();
$em->persist($history);
$em->flush();
With Doctrine ORM, Entities have an unique role : data containers!
According to Doctrine architecture, there is no reason to inject EntityManager inside.
If you need to do that, you're trying to put some code of the Business layer into layer.
So try to move your code into a service, like a manager for your Entity or if you're lazy in a controller but it's a bit crapy.
I would venture to first answer the question, and then give out advice.
If you look into source code of Doctrine2, you may to find this method in Doctrine\ORM\UnitOfWork:
/**
* #param ClassMetadata $class
*
* #return \Doctrine\Common\Persistence\ObjectManagerAware|object
*/
private function newInstance($class)
{
$entity = $class->newInstance();
if ($entity instanceof \Doctrine\Common\Persistence\ObjectManagerAware) {
$entity->injectObjectManager($this->em, $class);
}
return $entity;
}
So... it means, if your entity implements \Doctrine\Common\Persistence\ObjectManagerAware you will have EntityManager inside Doctrine2 entity. That's it.
Now advice:
IT'S REALLY BAD PRACTICE, AND NOT RECOMMENDED FOR USE.
From PhpDoc of \Doctrine\Common\Persistence\ObjectManagerAware interface:
Word of Warning: This is a very powerful hook to change how you can work with your domain models.
Using this hook will break the Single Responsibility Principle inside your Domain Objects
and increase the coupling of database and objects.

Resources