I have a project with a lot of tests class like
class MyTest extends BaseTestCase
{
public function __construct()
{
parent::__construct();
$this->em = $this->get('doctrine')->getManager();
}
public function setUp() {
$this->init();
//load sql data for the tests
$path = $this->get('kernel')->locateResource('#Bundle/Data/Test.sql');
$content_file_sql_data = file_get_contents($path);
$stmt = $this->em->getConnection()->prepare($content_file_sql_data);
$stmt->execute();
$stmt->closeCursor();
}
/*
* Then we do a lot of tests using the database
*/
}
They all extends my BaseTestCase:
abstract class BaseTestCase extends \PHPUnit_Framework_TestCase {
protected $_container;
protected $kernel;
public function __construct() {
parent::__construct();
$this->kernel = new \AppKernel("test", true);
$this->kernel->boot();
$this->_container = $this->kernel->getContainer();
$this->init();
}
//empty the database before each test class
public function init() {
$this->_application = new Application($this->kernel);
$this->_application->setAutoExit(false);
//rebuild and empty the database
$this->runConsole("doctrine:schema:drop", array("--force" => true));
$this->runConsole("doctrine:schema:create");
}
Since I have a lot of tests, i have recently got some errors PDOException: SQLSTATE[08004] [1040] Too many connections. It's like phpunit never close database connection, and around 100 tests I get this error for all the other tests.
How can i fix it?
I tried to put a last test doing $this->em->close() at the end of each test class but it didn't solve it
Some additional information: i'm pretty sure I don't have an issue with ONE test, because if I change the order of the test suite, the error appears around the same amount of tests class passed
My solution was to override shutdown method in my Bundle class:
public function shutdown()
{
if ('test' == $this->container->getParameter('kernel.environment')) {
/* #var EntityManager $em */
$em = $this->container->get('doctrine.orm.default_entity_manager');
$em->getConnection()->close();
}
}
Related
I try to follow this tutorial : https://www.thinktocode.com/2018/03/05/repository-pattern-symfony/.
It's suppose to help structure your Repository.
But when i get to this point :
final class ProductRepository
{
/**
* #var EntityManagerInterface
*/
private $entityManager;
/**
* #var ObjectRepository
*/
private $objectRepository;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
$this->objectRepository = $this->entityManager->getRepository(Product::class);
}
public function find(int $productId): Product
{
$product = $this->objectRepository->find($productId);
return $product;
}
public function findOneByTitle(string $title): Product
{
$product = $this->objectRepository
->findOneBy(['title' => $title]);
return $product;
}
public function save(Product $product): void
{
$this->entityManager->persist($product);
$this->entityManager->flush();
}
}
And testing my Repository with this test case :
<?php
namespace App\Tests\Repository;
use App\Repository\ProductRepository;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class ProductRepository_KernelTest extends KernelTestCase
{
private ?ProductRepository $_productRepository;
protected function setUp(): void
{
$kernel = self::bootKernel();
$this->_productRepository = self::$container->get(ProductRepository::class);
}
public function test_findAllProductNatByLabelForLabelEmptyReturnTenProduct()
{
dump($this->_productRepository->findAllProductsByLabel('AACIFEMINE'));
die();
}
}
It loop endlessly.
I think it's due to this code :
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
$this->objectRepository = $this->entityManager->getRepository(Product::class); // <-----
}
As it call the ProductRepository constructor inside of this same constructor... So i guess it's why that loop
So I don't know. Is this tutorial just wrong or not up to date ?
https://www.thinktocode.com/2018/03/05/repository-pattern-symfony/#comment-4155200782
Maciej,
You are correct that in these example we are using 2 repositories. The
object repository from doctrine inside our own custom repository. This
allows use to be decoupled from doctrine's repository and still change
this in the future. This means to not set your custom repository as
the default repository in your entity.
You can get rid of inject the object repository, and in so only be
using 1 repository by implementing a BaseRepository class in which you
create the basic findBy, findOneBy, createQueryBuilder yourself. Take
a look at the EntityRepository in Doctrine/ORM. This might be a good
follow up topic to go over in a future article to create a better
solution then I suggested in here.
I use Symfony 4 with messenger and I use a worker who consumes my messages as a long-running process.
I have a bug with doctrine if I delete my post and recreate a new one and I dispatch my message. $post have the old data and not the new one.
I have tried a lot of things and nothing work, it works when I restart my worker.
class ChannelMessageHandler implements MessageHandlerInterface
{
private $channel;
private $bus;
public function __construct(ChannelService $channel, MessageBusInterface $commandBus)
{
$this->channel = $channel;
$this->bus = $commandBus;
}
public function __invoke(ChannelMessage $channelMessage)
{
$error = $this->channel->handleChannel($channelMessage->getUser(), $channelMessage->getService());
if ($error) {
throw new Exception($error[0]);
}
$this->bus->dispatch(new FeedMessage($channelMessage->getUser(), $channelMessage->getService()));
}
}
}
My MessageHandler call a service :
class ChannelService implements ContainerAwareInterface
{
use ContainerTrait;
protected $em;
protected $logger;
public function __construct(EntityManagerInterface $entityManager, LoggerInterface $logger)
{
$this->em = $entityManager;
$this->logger = $logger;
}
public function handleChannel($userId, $serviceName)
{
$user = $this->em->getRepository('App:User\Authentication\User')->findOneById($userId);
$post = $user->getPost();
return $this->getUserAnalyticBy($post, $serviceName);
}
thanks a lot
I decided, that it will be fine if I use data providers but when i try to generate code coverage whole tested class has 0% coverage.. Can someone tell me why?
Test class:
class AuthorDbManagerTest extends AbstractDbManagerTest
{
public function setUp()
{
parent::setUp();
}
/**
* #dataProvider instanceOfProvider
* #param bool $isInstanceOf
*/
public function testInstances(bool $isInstanceOf)
{
$this->assertTrue($isInstanceOf);
}
public function instanceOfProvider()
{
$manager = new AuthorDbManager($this->getEntityManagerMock());
return [
"create()" => [$manager->create() instanceof Author],
"save()" => [$manager->save(new Author()) instanceof AuthorDbManager],
"getRepository" => [$manager->getRepository() instanceof EntityRepository],
];
}
}
Tested class:
class AuthorDbManager implements ManagerInterface
{
protected $entityManager;
protected $repository;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
$this->repository = $entityManager->getRepository(Author::class);
}
public function create(array $data = [])
{
return new Author();
}
public function getRepository(): EntityRepository
{
return $this->repository;
}
public function save($object): ManagerInterface
{
$this->entityManager->persist($object);
$this->entityManager->flush();
return $this;
}
}
Why my code coverage is 0% on AuthorDbManager?
Screen
The data in the DataProvider is collected before the actual tests start - and there is nothing useful being tested within the testInstances() method.
If you passed the classname and expected class into testInstances($methodName, $expectedClass):
public function testInstances(callable $method, $expectedClass)
{
$this->assertInstanceOf($expectedClass, $method());
}
The dataprovider could return a callable, and the expected result:
"create()" => [[$manager,'create'], Author::class],
then you'd at least be running the code with in the actual test. You may also be better to just pass back a string methodname - 'create', and run that with a locally created $manager instance - $manager->$method() in the test.
In general, it's best to test something as specific as you can - not just letting it convert to a true/false condition.
I have a Symfony 3.2 project with a backend. Each entity has its CRUD Controllers, Views etc. I have prepared an
abstract class AbstractControllerTest extends WebTestCase that is a base for tests for each entity. For each entity I use a simple test that asserts that list, show, edit and new returns HTTP 200.
So when I run all test it test list, show etc for each Entity. The problem is that in list Controller I use KNPPaginator with default order. The Controller works OK but when I run tests and it gets to the second entity I get 500 error because of a missing entity field. It turns out that the test takes a list Query for Pager from previous test.
So Entity A is ordered by default with a position field. Entity B doesn't have position field and that cause the error. So when PHPUnit goes to test A Entity it is OK, then it moves to test B Entity and then there is an error.
I don't know what is going on because ordering is not saved in session so there is no way that PHPUnit gets query from session from previous Entity.
Any ideas what is going on?
AbstractControllerTest
abstract class AbstractControllerTest extends WebTestCase
{
/** #var Client $client */
public $client = null;
protected $user = '';
protected $prefix = '';
protected $section = '';
protected $entityId = '';
public function setUp()
{
$this->client = $this->createAuthorizedClient();
}
/**
* #return Client
*/
protected function createAuthorizedClient()
{
$client = static::createClient();
$client->setServerParameter('HTTP_HOST', $client->getContainer()->getParameter('test_info_domain'));
$client->setServerParameter('HTTPS', true);
$client->followRedirects();
$container = $client->getContainer();
$session = $container->get('session');
/** #var $userManager \FOS\UserBundle\Doctrine\UserManager */
$userManager = $container->get('fos_user.user_manager');
/** #var $loginManager \FOS\UserBundle\Security\LoginManager */
$loginManager = $container->get('fos_user.security.login_manager');
$firewallName = $this->section;
/** #var UserInterface $userObject */
$userObject = $userManager->findUserBy(array('username' => $this->user));
$loginManager->logInUser($firewallName, $userObject);
// save the login token into the session and put it in a cookie
$container->get('session')->set('_security_' . $firewallName,
serialize($container->get('security.token_storage')->getToken()));
$container->get('session')->save();
$client->getCookieJar()->set(new Cookie($session->getName(), $session->getId()));
return $client;
}
public function testIndex()
{
//CRUD index
$this->client->request('GET', sprintf('/%s/%s',$this->section,$this->prefix));
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
}
public function testShow()
{
//CRUD show
$this->client->request('GET', sprintf('/%s/%s/%s/show',$this->section,$this->prefix, $this->entityId));
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
}
public function testEdit()
{
//CRUD edit
$this->client->request('GET', sprintf('/%s/%s/%s/edit',$this->section,$this->prefix, $this->entityId));
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
}
public function testNew()
{
//CRUD new
$this->client->request('GET', sprintf('/%s/%s/new',$this->section,$this->prefix));
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
}
}
And an example of one of the test class for Controller for one Entity
class AgendaCategoryControllerTest extends AbstractControllerTest
{
protected $user = 'tom#test.com';
protected $section = 'admin';
protected $prefix = 'agenda-category';
protected $entityId = '40';
}
If I run separately
php phpunit.phar src/Bundle/Tests/Controller/Admin/AControllerTest.php
and
php phpunit.phar src/Bundle/Tests/Controller/Admin/BControllerTest.php
it is OK.
If run together there is this weird bug
php phpunit.phar -c phpunit.xml.dist --testsuite=Admin
You can reset your test client between tests by doing the following in your setUp-method:
public function setUp()
{
$this->client = $this->createAuthorizedClient();
$this->client->restart();
}
You might have to move the restart into your createAuthorizedClient-method to ensure it does not reset your auth info.
We've built up a set of data fixtures to seed the database with all our reference values. We are also using the DoctrineMigrationsBundle to manage schema updates. We would like to trigger the fixture load within our initial schema migration class so the system gets populated before running any additional schema updates.
I found in the docs that you can make migration classes container aware, but I can't figure out how to jump from that to calling/running the data fixtures. I haven't found any good answers on Stackoverflow or via google. Has anyone done this and can point me in the right direction? (or have suggestions on a better way to manage seed data in conjunction with schema migrations). Thanks.
This is using Symfony Version: 2.4
This is interesting question. I've found the "dirty" solution, but it works well.
namespace Application\Migrations;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
class Version20140811164659 extends AbstractMigration implements ContainerAwareInterface
{
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function up(Schema $schema)
{
// ... your code here
}
public function postUp(Schema $schema)
{
// here you have to define fixtures dir
$this->loadFixtures('src/Acme/BlogBundle/DataFixtures/ORM');
}
public function down(Schema $schema)
{
// ... your code here
}
public function loadFixtures($dir, $append = true)
{
$kernel = $this->container->get('kernel');
$application = new \Symfony\Bundle\FrameworkBundle\Console\Application($kernel);
$application->setAutoExit(false);
//Loading Fixtures
$options = array('command' => 'doctrine:fixtures:load', "--fixtures" => $dir, "--append" => (boolean) $append);
$application->run(new \Symfony\Component\Console\Input\ArrayInput($options));
}
}
This solution simply running console command php app/console doctrine:fixtures:load --fixtures=src/Acme/BlogBundle/DataFixtures/ORM --append after "up" migration.
Sorry for poore English. If you'll find clear solution, share it ;)
I've made a migration class to address this very problem. The code is essentially inspired from the doctrine:fixtures:load command.
<?php
namespace AppBundle\Migrations;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
abstract class AbstractFixturesAwareMigration extends AbstractMigration implements ContainerAwareInterface
{
private $container;
private $fixtures;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
protected function getContainer()
{
return $this->container;
}
protected function addFixture(FixtureInterface $fixture)
{
if(null === $this->fixtures) {
$this->fixtures = new ContainerAwareLoader($this->getContainer());
}
$this->fixtures->addFixture($fixture);
return $this;
}
protected function executeFixtures($em = null, $append = true, $purgeMode = ORMPurger::PURGE_MODE_DELETE)
{
$em = $this->getContainer()->get('doctrine')->getManager($em);
$purger = new ORMPurger($em);
$purger->setPurgeMode($purgeMode);
$executor = new ORMExecutor($em, $purger);
$executor->execute($this->fixtures->getFixtures(), $append);
$this->fixtures = null;
return $this;
}
}
Usage is pretty straightforward:
<?php
namespace Application\Migrations;
use AppBundle\Migrations\AbstractFixturesAwareMigration
use Doctrine\DBAL\Schema\Schema;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Auto-generated Migration: Please modify to your needs!
*/
class Version20170726102103 extends AbstractFixturesAwareMigration
{
/**
* #param Schema $schema
*/
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
// [...]
}
public function postUp(Schema $schema)
{
// LoadMyData can be any fixture class
$this->addFixture(new LoadMyData());
$this->executeFixtures();
}
/**
* #param Schema $schema
*/
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
// [...]
}
}