Testing fixtures with addReference in Symfony 4 - symfony

I'm working on a Symfony's project and i have some issues while testing with phpunit.
I have StatusFixtures with addReference to be used in BriefFixtures and this work correctly when i do doctrine:fixtures:load (with correct dependency to load Status before Brief).
But, when i run my tests, using those fixtures, the following error is coming : Error: Call to a member function addReference() on null
My StatusFixtures.php
<?php
namespace App\DataFixtures;
use App\Entity\Status;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
class StatusFixtures extends Fixture
{
const Status_Reference = 'status';
public function load(ObjectManager $manager)
{
// some code to assign values
$manager->persist($activeStatus);
$this->addReference(self::Status_Reference, $activeStatus);
$manager->flush();
}
}
My BriefFixtures.php
<?php
namespace App\DataFixtures;
use App\Entity\Brief;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
class BriefFixtures extends Fixture implements DependentFixtureInterface
{
public function load(ObjectManager $manager)
{
// some code to assign values
$briefValid->setStatus($this->getReference(StatusFixtures::Status_Reference));
$manager->persist($briefValid);
$manager->flush();
}
public function getDependencies()
{
return array(
StatusFixtures::class,
);
}
}
And i'm loading fixtures this way in my tests
private $entityManager;
protected function setUp()
{
$kernel = self::bootKernel();
$this->entityManager = $kernel->getContainer()
->get('doctrine')
->getManager();
$status = new StatusFixtures();
$status->load($this->entityManager);
$fixture = new BriefFixtures();
$fixture->load($this->entityManager);
}
And my error Error: Call to a member function addReference() on null
$this from StatusFixtures seems to be null, but i don't understand why this correctly works when i'm loading fixtures and no more when i'm running my tests.
Maybe something is missing in setUp() ?
Thank you for your help

The problem lies in the Symfony documentation for Fixtures. It makes you feel like
$fixture->load($this->entityManager); will simply load the fixture, but that is not true. It is simple when you use the command doctrine:fixtures:load because it does more than just above load function call.
Going with third party solutions will be the quickest and probably the best solution. Here are few libraries that you can use:
liip/LiipTestFixturesBundle
hautelook/AliceBundle (thanks michal)
The error you are getting comes from ReferenceRepository object that is supposed to store the references, but it is null. Who actually sets up this repository, it is Doctrine\Common\DataFixtures\Executor\AbstractExecutor. What you need is a Loader that loads the fixture by creating everything needed for it to work. One of those loaders is Doctrine\Bundle\FixturesBundle\Loader\SymfonyFixturesLoader that your command doctrine:fixtures:load is using. You can use that loader or write your own loader. You can see that what this loader has to do to provide you the expected results. But that still is not it, you also need Doctrine\Common\DataFixtures\Executor\ORMExecutor because your Fixture is a database entity and you need to persist it. You can see that
how doctrine:fixtures:load makes use of SymfonyFixturesLoader and ORMExecutor to provide you expected result. So, you will have to write your own solution for this. I worte a Loader for myself before because I didn't want to go with third party solutions. I am putting it below. It may not serve your purpose exactly, but it will give you ideas how to write your own Loader if you want to.
namespace App\Tests\Extra;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\Loader;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\DBAL\Platforms\MySqlPlatform;
use Doctrine\ORM\EntityManagerInterface;
use App\Tests\Extra\Exception\FixtureNotLoadedException;
class FixtureLoader
{
private $entityManager;
private $loader;
private $registry;
public function __construct(
EntityManagerInterface $entityManager,
ManagerRegistry $registry
) {
$this->entityManager = $entityManager;
$this->registry = $registry;
}
public function loadFixtures(array $classNames) : void
{
$this->loader = new Loader();
foreach ($classNames as $className) {
$this->loader->addFixture(new $className());
}
$executor = new ORMExecutor($this->entityManager, new ORMPurger());
$executor->execute($this->loader->getFixtures());
}
public function getFixture(string $className) : Fixture
{
if ($this->loader == null) {
throw new FixtureNotLoadedException(
sprintf(
'The fixture %s must be loaded before you can access it.',
$className
)
);
}
return $this->loader->getFixture($className);
}
private function getPurger() : ORMPurger
{
$purger = new ORMPurger($this->entityManager);
$purger->setPurgeMode(ORMPurger::PURGE_MODE_TRUNCATE);
return $purger;
}
public function cleanDatabase() : void
{
$connection = $this->entityManager->getConnection();
$mysql = ('ORM' === $this->registry->getName()
&& $connection->getDatabasePlatform() instanceof MySqlPlatform);
if ($mysql) {
$connection->query('SET FOREIGN_KEY_CHECKS=0');
}
$this->getPurger()->purge();
if ($mysql) {
$connection->query('SET FOREIGN_KEY_CHECKS=1');
}
}
}

Related

Symfony 6 - Attempted to call an undefined method named "getDoctrine" [duplicate]

As my IDE points out, the AbstractController::getDoctrine() method is now deprecated.
I haven't found any reference for this deprecation neither in the official documentation nor in the Github changelog.
What is the new alternative or workaround for this shortcut?
As mentioned here:
Instead of using those shortcuts, inject the related services in the constructor or the controller methods.
You need to use dependency injection.
For a given controller, simply inject ManagerRegistry on the controller's constructor.
use Doctrine\Persistence\ManagerRegistry;
class SomeController {
public function __construct(private ManagerRegistry $doctrine) {}
public function someAction(Request $request) {
// access Doctrine
$this->doctrine;
}
}
You can use EntityManagerInterface $entityManager:
public function delete(Request $request, Test $test, EntityManagerInterface $entityManager): Response
{
if ($this->isCsrfTokenValid('delete'.$test->getId(), $request->request->get('_token'))) {
$entityManager->remove($test);
$entityManager->flush();
}
return $this->redirectToRoute('test_index', [], Response::HTTP_SEE_OTHER);
}
As per the answer of #yivi and as mentionned in the documentation, you can also follow the example below by injecting Doctrine\Persistence\ManagerRegistry directly in the method you want:
// src/Controller/ProductController.php
namespace App\Controller;
// ...
use App\Entity\Product;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Response;
class ProductController extends AbstractController
{
/**
* #Route("/product", name="create_product")
*/
public function createProduct(ManagerRegistry $doctrine): Response
{
$entityManager = $doctrine->getManager();
$product = new Product();
$product->setName('Keyboard');
$product->setPrice(1999);
$product->setDescription('Ergonomic and stylish!');
// tell Doctrine you want to (eventually) save the Product (no queries yet)
$entityManager->persist($product);
// actually executes the queries (i.e. the INSERT query)
$entityManager->flush();
return new Response('Saved new product with id '.$product->getId());
}
}
Add code in controller, and not change logic the controller
<?php
//...
use Doctrine\Persistence\ManagerRegistry;
//...
class AlsoController extends AbstractController
{
public static function getSubscribedServices(): array
{
return array_merge(parent::getSubscribedServices(), [
'doctrine' => '?'.ManagerRegistry::class,
]);
}
protected function getDoctrine(): ManagerRegistry
{
if (!$this->container->has('doctrine')) {
throw new \LogicException('The DoctrineBundle is not registered in your application. Try running "composer require symfony/orm-pack".');
}
return $this->container->get('doctrine');
}
...
}
read more https://symfony.com/doc/current/service_container/service_subscribers_locators.html#including-services
In my case, relying on constructor- or method-based autowiring is not flexible enough.
I have a trait used by a number of Controllers that define their own autowiring. The trait provides a method that fetches some numbers from the database. I didn't want to tightly couple the trait's functionality with the controller's autowiring setup.
I created yet another trait that I can include anywhere I need to get access to Doctrine. The bonus part? It's still a legit autowiring approach:
<?php
namespace App\Controller;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\ObjectManager;
use Symfony\Contracts\Service\Attribute\Required;
trait EntityManagerTrait
{
protected readonly ManagerRegistry $managerRegistry;
#[Required]
public function setManagerRegistry(ManagerRegistry $managerRegistry): void
{
// #phpstan-ignore-next-line PHPStan complains that the readonly property is assigned outside of the constructor.
$this->managerRegistry = $managerRegistry;
}
protected function getDoctrine(?string $name = null, ?string $forClass = null): ObjectManager
{
if ($forClass) {
return $this->managerRegistry->getManagerForClass($forClass);
}
return $this->managerRegistry->getManager($name);
}
}
and then
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Entity\Foobar;
class SomeController extends AbstractController
{
use EntityManagerTrait
public function someAction()
{
$result = $this->getDoctrine()->getRepository(Foobar::class)->doSomething();
// ...
}
}
If you have multiple managers like I do, you can use the getDoctrine() arguments to fetch the right one too.

How to get entity instance when doing unit test using Symfony and PHPUnit?

I'm trying to do a unit test in the environment as the title says. However, the method contained in the class under test requires an entity instance as an argument. So I'm trying to get the above entity in a generic homebrew test class that extends the TestCase class but I can't figure out how to do it.
I have little experience with functional testing. I used fixtures at that time, so I'm guessing that I should use fixtures this time too. Is that correct? I would appreciate if you could teach me how to do that as well.
Please let me know, even a little information.
below is the test class, and the interface of the test target.
ps. we are using nelmio/alice bundle.
<?php
declare(strict_types=1);
namespace xxx\xxxx\Tests\Customize\Server\AlladinOffice;
use Codeception\PHPUnit\TestCase;
use Customize\Service\AlladinOffice\CodeFormatter;
use Customize\Service\AlladinOffice\CustomerCode;
use Doctrine\Common\Persistence\ObjectManager;
use Eccube\Repository\CustomerRepository;
use PHPUnit\Framework\MockObject\MockObject;
use Proxies\__CG__\Eccube\Entity\Customer;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class CustomerCodeTest extends KernelTestCase
{
/**
* #var CustomerCode
*/
private $sut;
/**
* #var CustomerRepository|MockObject
*/
private $customerRepository;
protected function setUp()
{
$codeFormatter = new CodeFormatter();
$this->sut = new CustomerCode($codeFormatter);
$this->customerRepository = $this->createMock(CustomerRepository::class);
parent::setUp();
}
public function testGetCustomerCode()
{
$actual = $this->sut->getCustomerCode(null);
$expected = 1000000000;
$this->assertEquals($expected, $actual);
// $customer = $this->customerRepository->findBy([], ['id' => 'ASC']);
// dump($customer);exit();
// $actual = $this->sut->getCustomerCode($customer);
$customer = new Customer;
//$customer->set
//$this->customerRepository
//->expects($this->any())
//->method('find')
//->willReturn($);
}
}
<?php
declare(strict_types=1);
namespace Customize\Service\AlladinOffice;
use Eccube\Entity\Customer;
interface CustomerCodeInterface
{
public function getCustomerCode(?Customer $customer): string;
public function getChannelCode(?Customer $customer): string;
public function getRankCode(?Customer $customer): string;
}
for example a detailed method to test.
public function getCustomerCode(?Customer $customer): string
{
if (!$customer) {
return self::DEFAULT_CUSTOMER_CODE;
}
$customerRank = $customer->getXxxCustomer()->getCustomerRank();
$customerChannel = $customerRank ? $customerRank->getCustomerChannel() : null;
if (!$customerChannel || !$customerChannel->isExportToAO()) {
return self::DEFAULT_CUSTOMER_CODE;
}
return $this->formatter->getCustomerCodeFromCustomerId($customer->getId());
}

Dependency Injection with Fixtures

I am building a web application using Symfony 4. I am trying to load fixtures from my functional tests. I have created a fixture class:
<?php
namespace App\DataFixtures;
use App\Entity\User;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class TestFixtures extends Fixture
{
private $encoder;
public function __construct(UserPasswordEncoderInterface $encoder)
{
$this->encoder = $encoder;
}
public function load(ObjectManager $em)
{
$user = new User();
$user->setUsername('user#gamil.com');
$user->setIsActive(true);
$user->setEmail('user#gmail.com');
$user->setFirstName('John');
$user->setLastName('Doe');
$user->setSchool($school);
$user->setRoles(['ROLE_USER']);
$password = $this->encoder->encodePassword($user, 'pass1234');
$user->setPassword($password);
$em->persist($user);
$em->flush();
return $user;
}
}
I have configured services_test.yaml to pass the service to the fixture:
services:
_defaults:
public: true
App\DataFixtures\TestFixtures:
arguments: ['#security.password_encoder']
However, when I try to run my tests I get the following error:
ArgumentCountError: Too few arguments to function App\DataFixtures\TestFixtures::__construct(), 0 passed in /Development/app/src/Test/ApiTestCase.php on line 38 and exactly 1 expected
This is the function from my test case:
protected function setUp()
{
$this->client = self::$staticClient;
$this->purgeDatabase();
$em = $this->getEntityManager();
$fixture = new TestFixtures();
$fixture->load($em);
}
Thanks in advance for any advice offered on this...
You are instantiating a class:
$fixture = new TestFixtures(); whereas you need it as a service.
You need to either, do this $fixture = new TestFixtures(encoder);
where encoder is either an instantiated class or a mock of your encoder class, or, retrieve TestFixtures class from the client's container. I'm not entirely sure but try
$fixture = $this->client->getContainer()->get('App\DataFixtures\TestFixtures');

How to load Symfony2 fixtures from migration class?

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
// [...]
}
}

Laravel facade class not found when mocked in a unit test

I might be missing something here but I have a very simple helper class that creates a directory:
// Helper class
<?php namespace MyApp\Helpers;
use User;
use File;
class FileSystemHelper
{
protected $userBin = 'users/uploads';
public function createUserUploadBin(User $user)
{
$path = $this->userBin . '/' . $user->id;
if ( ! File::isDirectory($path))
{
File::makeDirectory($path);
}
}
}
And associated test here:
// Associated test class
<?php
use MyApp\Helpers\FileSystemHelper;
class FileSystemHelperTest extends TestCase {
protected $fileSystemHelper;
public function setUp()
{
$this->fileSystemHelper = new FileSystemHelper;
}
public function testNewUploadBinCreatedWhenNotExists()
{
$user = new User; // this would be mocked
File::shouldReceive('makeDirectory')->once();
$this->fileSystemHelper->createUserUploadBin($user);
}
}
However I get a fatal error when running the test:
PHP Fatal error: Class 'File' not found in /my/app/folder/app/tests/lib/myapp/helpers/FileSystemHelperTest.php
I've looked at the docs for mocking a facade and I can't see where I'm going wrong. Any suggestions?
Thanks
I missed this in the docs:
Note: If you define your own setUp method, be sure to call parent::setUp.
Calling that cured the problem. Doh!
it's because laravel framework is not loaded before using facade OR it because you are not using laravel php unit(TestCase class)
here is a sample code to test case that are in app/
//attention to extends from TestCase not ()
/**
* TEST CASE the application.
*
*/
class TestCase extends Illuminate\Foundation\Testing\TestCase {
/**
* Creates the application.
*
* #return \Symfony\Component\HttpKernel\HttpKernelInterface
*/
public function createApplication()
{
$unitTesting = true;
$testEnvironment = 'testing';
//this line boot the laravel framework so all facades are in your hand
return require __DIR__.'/../../bootstrap/start.php';
}
}

Resources