Mock non-existing class in PHPUnit 10 - phpunit

Before PHPUnit 10 it was possible to mock non-existing class using next code:
$this->getMockBuilder('NonExistentClass')
->setMethods(['__invoke'])
->getMock();
In PHPUnit 10 the setMethods() is going to be removed: https://github.com/sebastianbergmann/phpunit/issues/3769
New MockBuilder API introduced addMethods() method which is using Reflection inside and does not allow to work with non-existing classes anymore.
Please advise how we can create mocks for non-existing class with new API

You can mock \stdClass as the base class:
$mock = $this->getMockBuilder(\stdClass::class)->addMethods(['__invoke'])->getMock();

I finished with next solution:
in my tests folder I've created an empty "physical" class:
<?php
namespace Tests\Helpers;
class EmptyCallableClass
{
public function __invoke()
{
}
}
then in my tests I can mock it like all others classes (example from Laravel):
<?php
namespace Tests\Unit;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\Helpers\EmptyCallableClass;
use Tests\TestCase;
class LravelMiddlewareTest extends TestCase
{
/**
* #var MockObject
*/
private $closureMock;
public function setUp(): void
{
$this->closureMock = $this->createPartialMock(EmptyCallableClass::class, ['__invoke']);
}
}

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.

Symfony dependency injection or service location for Repositories in PHPUnit tests

I'm writing a test for my code in API Platform (built on Symfony 5) and PHPUnit.
I've created a base testcase named TestContext that extends ApiTestCase. All my tests classes will extend TestContext to reuse shared testing boilerplate code.
I'm also trying to use dependency injection (or service location) to resolve the Repository class, so I can access them in all the child classes.
What would be the cleanest, easiest and best-practice way to have access to my Repositories in my test classes?
In order to inject the Repository for the API tests I have a src/Test/BaseApiTest.php
namespace App\Test;
use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase;
use Doctrine\ORM\EntityManagerInterface;
use Hautelook\AliceBundle\PhpUnit\ReloadDatabaseTrait;
class BaseApiTestCase extends ApiTestCase
{
use ReloadDatabaseTrait;
protected ?EntityManagerInterface $em;
protected function setUp(): void
{
$kernel = self::bootKernel();
$this->em = $kernel->getContainer()
->get('doctrine')
->getManager();
}
}
Then for the test in my root tests directory I access it as he following:
//tests/UserRessourceTest.php
<?php
namespace App\Tests;
use App\Entity\User;
use App\Test\BaseApiTestCase;
class UserResourceTest extends BaseApiTestCase
{
public function testDeleteAccount(): void
{
...
/** #var User $deletedUser */
$deletedUser = $this->em->getRepository(User::class)->findOneById($user->getId());
...
}
}
This works well for most of my cases. I hope it will work for yours ;)

Base class controller with global twig service

First of all, I have to say that I have been seeing answers and documentation for several days but none of them answer my question.
The only and simple thing I want to do is to use the twig service as a global service in a BaseController.
This is my code:
<?php
namespace App\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use App\Service\Configuration;
use App\Utils\Util;
abstract class BaseController extends Controller
{
protected $twig;
protected $configuration;
public function __construct(\Twig_Environment $twig,Configuration $configuration)
{
$this->twig = $twig;
$this->configuration = $configuration;
}
}
Then in all my controllers extend the twig and configuration service, without having to inject it again & again.
//...
//......
/**
* #Route("/configuration", name="configuration_")
*/
class ConfigurationController extends BaseController
{
public function __construct()
{
//parent::__construct();
$this->twig->addGlobal('menuActual', "config");
}
As you can see the only thing I want is to have some services global to have everything more organized and also to create some global shortcuts for all my controllers. In this example I am assigning a global variable to make a link active in the menu of my template and in each controller I have to add a new value for menuActual, for example in the UserController the variable would be addGlobal('menuActual', "users").
I think this should be in the good practices of symfony which I don't find :(.
Having to include the \Twig_Environment in each controller to assign a variable to the view seems very repetitive to me. This should come by default in the controller.
Thanks
I've had that problem as well - trying to not have to repeat a bit of code for every controller / action.
I solved it using an event listener:
# services.yaml
app.event_listener.controller_action_listener:
class: App\EventListener\ControllerActionListener
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
#src/EventListener/ControllerActionListener.php
namespace App\EventListener;
use App\Controller\BaseController;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
/**
* Class ControllerActionListener
*
* #package App\EventListener
*/
class ControllerActionListener
{
public function onKernelController(FilterControllerEvent $event)
{
//fetch the controller class if available
$controllerClass = null;
if (!empty($event->getController())) {
$controllerClass = $event->getController()[0];
}
//make sure your global instantiation only fires if the controller extends your base controller
if ($controllerClass instanceof BaseController) {
$controllerClass->getTwig()->addGlobal('menuActual', "config");
}
}
}

Is there a way to inject EntityManager into a service

While using Symfony 3.3, I am declaring a service like this:
class TheService implements ContainerAwareInterface
{
use ContainerAwareTrait;
...
}
Inside each action where I need the EntityManager, I get it from the container:
$em = $this->container->get('doctrine.orm.entity_manager');
This is a bit annoying, so I'm curious whether Symfony has something that acts like EntityManagerAwareInterface.
Traditionally, you would have created a new service definition in your services.yml file set the entity manager as argument to your constructor
app.the_service:
class: AppBundle\Services\TheService
arguments: ['#doctrine.orm.entity_manager']
More recently, with the release of Symfony 3.3, the default symfony-standard-edition changed their default services.yml file to default to using autowire and add all classes in the AppBundle to be services. This removes the need for adding the custom service and using a type hint in your constructor will automatically inject the right service.
Your service class would then look like the following:
use Doctrine\ORM\EntityManagerInterface;
class TheService
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
// ...
}
For more information about automatically defining service dependencies, see https://symfony.com/doc/current/service_container/autowiring.html
The new default services.yml configuration file is available here: https://github.com/symfony/symfony-standard/blob/3.3/app/config/services.yml
Sometimes I inject the EM into a service on the container like this in services.yml:
application.the.service:
class: path\to\te\Service
arguments:
entityManager: '#doctrine.orm.entity_manager'
And then on the service class get it on the __construct method.
Hope it helps.
I ran into the same issue and solved it by editing the migration code.
I replaced
$this->addSql('ALTER TABLE user ADD COLUMN name VARCHAR(255) NOT NULL');
by
$this->addSql('ALTER TABLE user ADD COLUMN name VARCHAR(255) NOT NULL DEFAULT "-"');
I don't know why bin/console make:entity doesn't prompt us to provide a default in those cases. Django does it and it works well.
So I wanted to answer your subquestion:
This is a bit annoying, so I'm curious whether Symfony has something
that acts like EntityManagerAwareInterface.
And I think there is a solution to do so (I use it myself).
The idea is that you slightly change your kernel so tha it checks for all services which implement the EntityManagerAwareInterface and injects it for them.
You can also add write an EntityManagerAwareTrait that implements the $entityManager property and the setEntityManager()setter. The only thing left after that is to implement/use the interface/trait couple the way you would do for the Logger for example.
(you could have done this through a compiler pass as well).
<?php
// src/Kernel.php
namespace App;
use App\Entity\EntityManagerAwareInterface;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use function array_key_exists;
class Kernel extends BaseKernel implements CompilerPassInterface
{
use MicroKernelTrait;
public function process(ContainerBuilder $container): void
{
$definitions = $container->getDefinitions();
foreach ($definitions as $definition) {
if (!$this->isAware($definition, EntityManagerAwareInterface::class)) {
continue;
}
$definition->addMethodCall('setEntityManager', [$container->getDefinition('doctrine.orm.default_entity_manager')]);
}
}
private function isAware(Definition $definition, string $awarenessClass): bool
{
$serviceClass = $definition->getClass();
if ($serviceClass === null) {
return false;
}
$implementedClasses = #class_implements($serviceClass, false);
if (empty($implementedClasses)) {
return false;
}
if (array_key_exists($awarenessClass, $implementedClasses)) {
return true;
}
return false;
}
}
The interface:
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\EntityManagerInterface;
interface EntityManagerAwareInterface
{
public function setEntityManager(EntityManagerInterface $entityManager): void;
}
The trait:
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\EntityManagerInterface;
trait EntityManagerAwareTrait
{
/** #var EntityManagerInterface */
protected $entityManager;
public function setEntityManager(EntityManagerInterface $entityManager): void
{
$this->entityManager = $entityManager;
}
}
And now you can use it:
<?php
// src/SomeService.php
declare(strict_types=1);
namespace App;
use Exception;
use App\Entity\EntityManagerAwareInterface;
use App\Entity\Entity\EntityManagerAwareTrait;
use App\Entity\Entity\User;
class SomeService implements EntityManagerAwareInterface
{
use EntityManagerAwareTrait;
public function someMethod()
{
$users = $this->entityManager->getRepository(User::Class)->findAll();
// ...
}
}

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