How to write PHP Unit Test for Static Methods - phpunit

I am very new to PHP Unit Testing. I am trying to create Unit Test for the following function:
$context = $args[0];
if (Subscriber::instance()->isSubscriber()) {
$context['body_class'] .= ' ' . $this->bodyClass;
}
return $context;
Which is very simple which adds classname in array if User is subscriber. Subscriber is class which has has a static instance method which returns true or false.
so far I have written this but I don't think this is correct:
$subscriber = $this->getMockBuilder(Subscriber::class)
->disableOriginalConstructor()
->setMethods(['isSubscriber'])
->getMock();
$subscriber->expects($this->once())
->method('isSubscriber')
->will($this->returnValue(true));
$this->assertInternalType('bool',$subscriber->isSubscriber());
Any help would be appreciated.

Yes, You can mock PHP static methods using PhpUnit together with Mockery!
Case: class to be tested Foo (Foo.php) uses the static method mockMe of the class ToBeMocked.
<?php
namespace Test;
use Test\ToBeMocked;
class Foo {
public static function bar(){
return 1 + ToBeMocked::mockMe();
}
}
ToBeMocked class (ToBeMocked.php)
<?php
namespace Test;
class ToBeMocked {
public static function mockMe(){
return 2;
}
}
The PhpUnit test file (FooTest.php) with mock of static method (using Mockery):
<?php
namespace Test;
use Mockery;
use Mockery\Adapter\Phpunit\MockeryTestCase;
// IMPORTANT: extends MockeryTestCase, NOT EXTENDS TestCase
final class FooTest extends MockeryTestCase
{
protected $preserveGlobalState = FALSE; // Only use local info
protected $runTestInSeparateProcess = TRUE; // Run isolated
/**
* Test without mock: returns 3
*/
public function testDefault()
{
$expected = 3;
$this->assertEquals(
$expected,
Foo::bar()
);
}
/**
* Test with mock: returns 6
*/
public function testWithMock()
{
// Creating the mock for a static method
Mockery::mock(
// don't forget to prefix it with "overload:"
'overload:Geko\MVC\Models\Test\ToBeMocked',
// Method name and the value that it will be return
['mockMe' => 5]
);
$expected = 6;
$this->assertEquals(
$expected,
Foo::bar()
);
}
}
Running this test:
$ ./vendor/phpunit/phpunit/phpunit -c testes/phpunit.xml --filter FooTest
PHPUnit 9.5.4 by Sebastian Bergmann and contributors.
Runtime: PHP 8.0.3
Configuration: testes/phpunit.xml
.. 2 / 2 (100%)
Time: 00:00.068, Memory: 10.00 MB
OK (2 tests, 3 assertions)

You can test (assert) static methods but you can't mock or stub them in PHPunit.
From the documentation:
Please note that final, private, and static methods cannot be stubbed
or mocked. They are ignored by PHPUnit’s test double functionality and
retain their original behavior except for static methods that will be
replaced by a method throwing a
\PHPUnit\Framework\MockObject\BadMethodCallException exception.

Related

Access Doctrine within a custom service in Symfony4

Aware that there is a lot of information around the net regarding this, I am still having a lot of trouble getting this to work.
I have created a custom service:
<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\AccommodationType;
use App\Entity\Night;
class AvailabilityChecks {
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function nightAvailable(string $RoomCode, string $NightDate) {
$GetRoom = $this->em->getDoctrine()->getRepository(AccommodationType::class)->findOneBy([
'RoomCode' => $RoomCode
]);
$RoomQnt = $GetRoom->getNightlyQnt();
$GetNight = $this->em->getDoctrine()->getRepository(Night::class)->findOneBy([
'RoomCode' => $RoomCode,
'NightDate' => $NightDate
]);
$NumberOfNights = $GetNight->count();
if($NumberOfNights<$RoomQnt) {
return true;
}
else {
return false;
}
}
}
and have put this in services.yaml:
AvailabilityChecks.service:
class: App\Service\AvailabilityChecks
arguments: ['#doctrine.orm.entity_manager']
So when I try and use this in my controller, I get this error:
Too few arguments to function App\Service\AvailabilityChecks::__construct(), 0 passed in /mypath/src/Controller/BookController.php on line 40 and exactly 1 expected
I just can't figure out why it's not injecting the ORM stuff into the constructor! Any help greatly appreciated
The problem is in your BookController. Even though you didn't posted its code I can assume you create new AvailabilityChecks in it (on line 40).
In Symfony every service is intantiated by service container. You should never intantiate service objects by yourself. Instead BookController must ask service container for AvailabilityChecks service. How should it do it ?
In Symfony <3.3 we used generally :
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class MyController extends Controller
{
public function myAction()
{
$em = $this->get('doctrine.orm.entity_manager');
// ...
}
}
Nowadays services can be injected in controllers using autowiring which is way easier:
use Doctrine\ORM\EntityManagerInterface;
class MyController extends Controller
{
public function myAction(EntityManagerInterface $em)
{
// ...
}
}
You are using the wrong service for what you want to do. The alias doctrine that is used, e.g. in the AbstractController when you call getDoctrine() is bound to the service Doctrine\Common\Persistence\ManagerRegistry.
So the code you wrote fits better with that and you should either add #doctrine or #Doctrine\Common\Persistence\ManagerRegistry to the service definition.
Both with your current configuration or the changed one, you don't have to call $this->em->getDoctrine(), because $this->em is already equivalent to $this->getDoctrine() from your controller. Instead you could create a (private) method to make it look more like that code, e.g.:
private function getDoctrine()
{
return $this->em;
}
Then you can call $this->getDoctrine()->getRepository(...) or use $this->em->getRepository(...) directly.
In Symfony 4, you dont need to create it as services. This is automatically now. Just inject the dependencies what you need in the constructor. Be sure that you have autowire property with true value in services.yml (it is by default)
Remove this from services.yml:
AvailabilityChecks.service:
class: App\Service\AvailabilityChecks
arguments: ['#doctrine.orm.entity_manager']
You dont need EntityManagerInterface because you are not persisting anything, so inject repositories only.
<?php
namespace App\Service;
use App\Entity\AccommodationType;
use App\Entity\Night;
use App\Repository\AccommodationTypeRepository;
use App\Repository\NightRepository;
class AvailabilityChecks {
private $accommodationTypeRepository;
private $nightRepository
public function __construct(
AcommodationTypeRepository $acommodationTypeRepository,
NightRepository $nightRepository
)
{
$this->acommodationTypeRepository = $acommodationTypeRepository;
$this->nightRepository = $nightRepository;
}
public function nightAvailable(string $RoomCode, string $NightDate) {
$GetRoom = $this->acommodationTypeRepository->findOneBy([
'RoomCode' => $RoomCode
]);
$RoomQnt = $GetRoom->getNightlyQnt();
$GetNight = $this->nightRepository->findOneBy([
'RoomCode' => $RoomCode,
'NightDate' => $NightDate
]);
$NumberOfNights = $GetNight->count();
if($NumberOfNights<$RoomQnt) {
return true;
}
else {
return false;
}
}
}
In SF4, you no longer need to specify dependencies required by your custom service in the service.yaml file. All you have to do is to use dependency injection.
So remove config lines, and call your service directly in the controller method :
<?php
namespace App\Controller;
use App\Service\AvailabilityChecks ;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class AppController extends AbstractController
{
public function index(AvailabilityChecks $service)
{
...
}
}
Having said that, i think you don't need custom service to do simple operations on database. Use repository instead.

Symfony2 : Doctrine : PHPUnit : Set entity Id during flushing with mocked entity manager in unit tests

Symfony 2.8.13 / Doctrine ORM 2.5.5 / PHPUnit 5.7.5
I want to test a method of a class that makes use of the doctrine entity manager. This public method calls a private one that instantiates a Bookmark entity, flushes it and returns this entity. Then later, in the tested method I need to access the entity Id. Everything is mocked excepted the Bookmark entity itself. The main problem is that there is no setId() method in my entity. Here is the code and my main idea to solve this issue but I don't know if it is correct ?
Tested class and method
class BookmarkManager
{
//...
public function __construct(TokenStorageInterface $tokenStorage, ObjectManager $em, Session $session)
{
//...
}
public function manage($bookmarkAction, $bookmarkId, $bookmarkEntity, $bookmarkEntityId)
{
//...
$bookmark = $this->add($bookmarkEntity, $bookmarkEntityId);
//...
$bookmarkId = $bookmark->getId();
//...
}
private function add($entity, $entityId)
{
//...
$bookmark = new Bookmark();
//...
$this->em->persist($bookmark);
$this->em->flush();
return $bookmark;
}
}
Test
class BookmarkManagerTest extends \PHPUnit_Framework_TestCase
{
public function testThatRestaurantAdditionToBookmarksIsWellManaged()
{
//...
// THIS WON'T WORK AS NO setId() METHOD EXISTS
$entityManagerMock->expects($this->once())
->method('persist')
->will($this->returnCallback(function ($bookmark) {
if ($bookmark instanceof Bookmark) {
$bookmark->setId(1);
}
}));
//...
$bookManager = new BookmarkManager($tokenStorageMock, $entityManagerMock, $sessionMock);
//...
}
}
Solutions ?
1- Make usage of reflection class as proposed here :
$entityManagerMock->expects($this->once())
->method('persist')
->will($this->returnCallback(function ($bookmark) {
if ($bookmark instanceof Bookmark) {
$class = new \ReflectionClass($bookmark);
$property = $class->getProperty('id');
$property->setAccessible(true);
$property->setValue($bookmark, 1);
//$bookmark->setId(1);
}
}));
2- Create a test Boookmark entity that extends from the real one and add a setId() method. Then create a mock of this class and replace and customize the one got from the ReturnCallback method with this one ? It seems crappy...
Any thoughts ? Thanks for your help.
The reflection looks interesting but it decreases readability of tests (mixing with mocks makes the situation tough).
I would create a fake for entity manager and implements there setting id based on reflection:
class MyEntityManager implements ObjectManager
{
private $primaryIdForPersitingObject;
public function __construct($primaryIdForPersitingObject)
{
$this->primaryIdForPersitingObject = $primaryIdForPersitingObject;
}
...
public function persist($object)
{
$reflectionClass = new ReflectionClass(get_class($object));
$idProperty = $reflectionClass->getProperty('id');
$idProperty->setAccessible(true);
$idProperty->setValue($object, $this->primaryIdForPersitingObject);
}
public function flush() { }
...
}
Once you implemented this, you can inject the instance of MyEntityManager and make your tests small and easier to maintain.
You test would look like
<?php
class BookmarkManagerTest extends \PHPUnit_Framework_TestCase
{
public function testThatRestaurantAdditionToBookmarksIsWellManaged()
{
// ...
$entityManager = MyEntityManager(1);
//...
$bookManager = new BookmarkManager($tokenStorageMock, $entityManager, $sessionMock);
//...
}
}
Of course, a situation may be harder if there is a need of setting different ids for many persisting objects. Then you can, for example, increase $primaryIdForPersitingObject on persist call
public function persist($object)
{
$reflectionClass = new ReflectionClass(get_class($object));
$idProperty = $reflectionClass->getProperty('id');
$idProperty->setAccessible(true);
$idProperty->setValue($object, $this->primaryIdForPersitingObject);
$this->primaryIdForPersitingObject++;
}
It may be extended even further to have separate primaryIdForPersitingObject each entity class, and your tests will be still clean.

Undefined variable in PHPUnit Test Class

I have a test class that reports an undefined variable and I cannot seem to understand what the issue is.
Basically the listener below is suppose to listen to an application boot event documented in the class below:
<?php
namespace Colleen\Core\Event\Application;
final class ApplicationBootedEvents
{
const APP_BOOTED = 'application.booted';
}
My event class is as shown below which receives an instance of the application itself.
<?php
namespace Colleen\Core\Event\Application;
use Symfony\Component\EventDispatcher\Event;
use Colleen\Core\Application;
/**
* The application.booted event is dispatched each time
* an application instance is created in the system.
*
*/
class ApplicationBootedEvent extends Event
{
protected $app;
public function __construct(Application $app)
{
$this->app = $app;
}
public function getApplication()
{
return $app;
}
}
These two classes to me look perfect according to Symfony's documentation on the Event Dispatcher Component. Following is the listener class that is suppose to listen to ApplicationBootedEvents::APP_BOOTED event.
<?php
namespace Colleen\Core\Event\Application\Listener;
use Colleen\Core\Event\Application\ApplicationBootedEvent;
class ApplicationBootedListener
{
public function onBoot(ApplicationBootedEvent $event)
{
$container = $event->getApplication()->getContainer();
$container->set('class.dispatcher', '\\Symfony\\Component\\EventDispatcher\\EventDispatcher');
}
}
The Listener class does nothing at the moment and my test case is to test whether the "class.dispatcher" key exist on my container which simple extends Pimple and is made available through the Application Object.
Below is my test that shows how these will eventually be used in my front controller or any class that stands between them and the front controller.
<?php
namespace Colleen\Qa\Core\Event\Application\Listener;
use Colleen\Core\Event\Application\Listener\ApplicationBootedListener;
use Colleen\Core\Event\Application\ApplicationBootedEvents;
use Colleen\Core\Event\Application\ApplicationBootedEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Colleen\Core\Container\Container;
use Colleen\Core\Application;
class AppliocationBootedListenerTest extends \PHPUnit_Framework_TestCase
{
public function testApplicationBootListener()
{
$dispatcher = new EventDispatcher();
$dispatcher->addListener(
ApplicationBootedEvents::APP_BOOTED, array(
new ApplicationBootedListener(), 'onBoot'
));
$app = $dispatcher->dispatch(ApplicationBootedEvents::APP_BOOTED, new ApplicationBootedEvent(new Application(new Container())))->getApplication();
$expected = '\\Symfony\\Component\\EventDispatcher\\EventDispatcher';
$actual = $app->getContainer()->get('class.dispatcher');
$this->assertSame($expected, $actual);
}
}
The idea is to test whether the Listener gets called and if it is able to feed our application object's container with all the necesary objects we will need to get our web framework to work.
Below is the output I get as a result if running this test case.
There's an error in your ApplicationBootedEvent.php file, on line 24 as the stack trace suggested..
Change
public function getApplication()
{
return $app;
}
To
public function getApplication()
{
return $this->app;
}

Symfony doctrine entity manager injected in thread

I'm trying to use doctrine entity manager in a thread. I use a static scope as suggested here .
Class A is a symfony service and doctrine entity manager is injected in service.yml
class A extends \Thread{
static $em;
public function __construct($em)
{
self::$em = $em;
}
public function run(){
self::$em->doSomething(); //here em is null
}
}
How i can use entity manager correctly from a thread?
UPDATE:
As #Mjh suggested I can't share entity manager from threads. I can have an istance of em in every threads however but this is very inefficient.
A solution could be build a container threaded class shared between threads in which I'll store the entities that return from doctrine queries. The entities obviously will be detached from entity manager but I need only a read cache shared between threads.
UPDATE2:
See my first answer
Open issue: avoid to initialize for every thread a new environment
We have built a doctrine cache shared between thread extending a Thread Safe Stackable.
Warning some parts of code are semplified for demo purpose.
class work extends \Collectable{
protected $parameters;
public static $doctrine_mongodb;
public function __construct($parameters){
$this->parameters = $parameters;
}
public function run()
{
try{
$loader = require __DIR__.'/../../../../../../vendor/autoload.php';
static::$container = unserialize($this->worker->container);
static::$doctrine_mongodb = static::$container->get('doctrine_mongodb');
...
DO WORK
$dm = static::$doctrine_mongodb->getManager();
$repo = $dm->getRepository('Bundle:Document');
$ris = $this->worker->doctrinecache->FindOneBy($repo, array('key' => $val));
...
}catch(\Exception $e){}
}
}
NB: in work class we have the parallel execution of work code and there we can safely use doctrine common cache.
It's not the same to share entity manager because document are detached but for read purpose is good. If somebody need to manage entities can use merge doctrine method.
class SWorker extends \Worker{
public $env;
public $debug;
public $mongodb_cache_engine;
public function __construct( $env, $debug, $doctrinecache, $workParams){
$this->workParams = $work;
$this->env = $env;
$this->debug = $debug;
$this->doctrinecache = $doctrinecache ;
}
public function start($options = null){
return parent::start(PTHREADS_INHERIT_NONE);
}
public function run(){
require_once __DIR__.'/../../../../../../app/bootstrap.php.cache';
require_once __DIR__.'/../../../../../../app/AppKernel.php';
$kernel = new \AppKernel($this->env, $this->debug);
$kernel->loadClassCache();
$kernel->boot();
$this->container = serialize($kernel->getContainer());
}
}
In Sworker class we prepare symfony environment for thread. Tnx to svenpelster https://github.com/krakjoe/pthreads/issues/369 for that.
class doctrinecache extends \Stackable{
public function __call($MethodName, $arguments){
$repository = array_shift($arguments);
$documentName = $repository->getDocumentName();
$hash = $this->generateHash($MethodName, $documentName, $arguments);
return $this->cacheIO($hash, $repository, $MethodName, $arguments);
}
public function cacheIO($hash, $repository, $MethodName, $arguments){
$result = isset($this["{$hash}"])? $this["{$hash}"] : NULL;
if(!$result){
$result = call_user_func_array(array($repository, $MethodName), $arguments);
$this["{$hash}"] = $result;
}
return $result;
}
}
And finally
$doctrineCache = $this->kernel->get('doctrineCacheService');
$pool = new \Pool($workerNumber, SWorker::class, [$this->kernel->getEnvironment(), $this->kernel->isDebug(), $doctrineCache ,$workParams]);
while(current($works ))
{
$pool->submit(current($works ));
next($works);
}
$pool->shutdown();
while(current($works ))
{
$arrayResults[] = current($works )->getResults();
next($works);
}

PHPUnit how to get a class property?

To test an API calling, I would like to test an entity lifecycle.
Get with no content
Post a content, returning an ID
Patch the content
Get with content
Delete the content
I would use the ID returned in the post method. I try with a private property in my test class but in each method test, the property is reseted. How I can to test with a dynamic variable in my test class?
An example of my code and the response from PHPUnit:
class CommentControllerTest extends PHPUnit_Framework_TestCase
{
private $commentId;
public function setUp()
{
}
public function testPostValidComment()
{
$this->commentId = 42;
}
public function testUpdateComment()
{
var_dump($this->commentId); // NULL
}
public function testDeleteComment()
{
var_dump($this->commentId); // NULL
}
}
Why my var_dump($this->commentId); returns NULL?
Use the #depends annotation for establishing the execution order and passing the value from one test to the next:
class CommentControllerTest extends PHPUnit_Framework_TestCase
{
public function testPostValidComment()
{
$commentId = 42;
return $commentId;
}
/**
* #depends testPostValidComment
*/
public function testUpdateComment($commentId)
{
return $commentId;
}
/**
* #depends testUpdateComment
*/
public function testDeleteComment($commentId)
{
}
}
Note:
if a test fails, other tests that depends on it will not run. So with the example setup:
test POST -> test UPDATE -> test DELETE
if the UPDATE fails you won't know if the DELETE works or not.
To solve this you can change the depends chain to be like this:
test POST -> test UPDATE
test POST -> test DELETE
So if the UPDATE fails, the DELETE will be tested. In this case, the POST test will be executed twice, once for the UPDATE and again for the DELETE.
PHPUnit docs on dependencies.

Resources