ZF2 unit-testing authentication - phpunit

I was learning about unit testing and I attempted to resolve the following issue:
Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for zfcUserAuthentication
... using the only answer given at:
Simple ZF2 Unit Tests for a controller using ZfcUser
So my setUp function looks the same. Unfortunately, I get the error message:
Zend\Mvc\Exception\InvalidPluginException: Plugin of type Mock_ZfcUserAuthentication_868bf824 is invalid; must implement Zend\Mvc\Controller\Plugin\PluginInterface
It is caused at this part of the code (split up in my code in the same way):
$this -> controller->getPluginManager()
->setService('zfcUserAuthentication', $authMock); // Error refers to this line.
The $authMock object is apparently not implementing plugininterface, which I need to implement to pass into setService.
Is $authMock not meant to be passed there for it's use in unit testing? Should I be using a different (unit-testing oriented) setService method?
I need a way to handle logging into my application, or my unit testing is pointless.
Thanks for any advice.
=== Edit (11/02/2013) ===
I wanted to focus on this part for clarification, as I think this is the problem area:
// Getting mock of authentication object, which is used as a plugin.
$authMock = $this->getMock('ZfcUser\Controller\Plugin\ZfcUserAuthentication');
// Some expectations of the authentication service.
$authMock -> expects($this->any())
-> method('hasIdentity')
-> will($this->returnValue(true));
$authMock -> expects($this->any())
-> method('getIdentity')
-> will($this->returnValue($ZfcUserMock));
// At this point, PluginManager disallows mock being assigned as plugin because
// it will not implement plugin interface, as mentioned.
$this -> controller->getPluginManager()
->setService('zfcUserAuthentication', $authMock);
If the mock doesn't handle necessary implementations, how else am I to pretend to login?

You have a problem with name-spacing or your autoloader.
When you are creating your mock, the class definition of ZfcUser\Controller\Plugin\ZfcUserAuthentication is not being found. So PHPUnit creates a mock that only extends this class for your test. If the class was available then PHPUnit will use the actual class to extend when making its mock, which will then use the parent classes/interfaces.
You can see this logic here: https://github.com/sebastianbergmann/phpunit-mock-objects/blob/master/PHPUnit/Framework/MockObject/Generator.php
if (!class_exists($mockClassName['fullClassName'], $callAutoload) &&
!interface_exists($mockClassName['fullClassName'], $callAutoload)) {
$prologue = 'class ' . $mockClassName['originalClassName'] . "\n{\n}\n\n";
if (!empty($mockClassName['namespaceName'])) {
$prologue = 'namespace ' . $mockClassName['namespaceName'] .
" {\n\n" . $prologue . "}\n\n" .
"namespace {\n\n";
$epilogue = "\n\n}";
}
$cloneTemplate = new Text_Template(
$templateDir . 'mocked_clone.tpl'
);
So if there is no class or interface, PHPUnit will actually create one itself so that the mock will meet the type hinting of original class name. However, any parent classes or interfaces will not be included because PHPUnit is not aware of them.
This would be due to not including the proper namespace in your test or having a problem in your autoloader. It is difficult to tell without actually seeing the entire test file.
Alternatively rather than mocking ZfcUser\Controller\Plugin\ZfcUserAuthentication, you could mock the Zend\Mvc\Controller\Plugin\PluginInterface in your test and pass that into the plugin manager. Though if you are type-hinting for the plugin in your code, your test still won't work.
//Mock the plugin interface for checking authorization
$authMock = $this->getMock('Zend\Mvc\Controller\Plugin\PluginInterface');
// Some expectations of the authentication service.
$authMock -> expects($this->any())
-> method('hasIdentity')
-> will($this->returnValue(true));
$authMock -> expects($this->any())
-> method('getIdentity')
-> will($this->returnValue($ZfcUserMock));
$this -> controller->getPluginManager()
->setService('zfcUserAuthentication', $authMock);

I just made an example for the FlashMessenger plugin. You should just use the ControllerPluginManager to override the ControllerPlugin. Make sure that your application bootstrap calls setApplicationConfig();
<?php
namespace SimpleTest\Controller;
use Zend\Test\PHPUnit\Controller\AbstractHttpControllerTestCase;
class SimpleControllerTest extends AbstractHttpControllerTestCase {
public function testControllerWillAddErrorMessageToFlashMessenger()
{
$flashMessengerMock = $this->getMockBuilder('\Zend\Mvc\Controller\Plugin\FlashMessenger', array('addErrorMessage'))->getMock();
$flashMessengerMock->expects($this->once())
->method('addErrorMessage')
->will($this->returnValue(array()));
$serviceManager = $this->getApplicationServiceLocator();
$serviceManager->setAllowOverride(true);
$serviceManager->get('ControllerPluginManager')->setService('flashMessenger', $flashMessengerMock);
$this->dispatch('/error/message');
}
}?>

Related

phpunit testable livewire fails with Undefined array key "fingerprint"

When trying a test that came with Laravel and Jetstream/Livewire libraries, I get an undefined array key "fingerprint" error message
Undefined array key "fingerprint"
at vendor/livewire/livewire/src/Testing/TestableLivewire.php:181
public function pretendWereSendingAComponentUpdateRequest($message, $payload)
{
$result = $this->callEndpoint('POST', '/livewire/message/'.$this->componentName, [
'fingerprint' => $this->payload['fingerprint'],
'serverMemo' => $this->payload['serverMemo'],
'updates' => [['type' => $message, 'payload' => $payload]],
]);
This happens for any out of the box feature tests that ship with Laravel9 with Jetstream when used against my project.
Here is one example that fails at the Livewire::test.... line.
The user is created and authenticating without issue and confirmed in other phpunit tests.
class BrowserSessionsTest extends TestCase
{
use RefreshDatabase;
public function test_other_browser_sessions_can_be_logged_out(): void
{
$this->actingAs($user = User::factory()->create());
Livewire::test(LogoutOtherBrowserSessionsForm::class)
->set('password', $user->password)
->call('logoutOtherBrowserSessions')
->assertSuccessful();
}
}
I stood up a fresh Laravel 9 project which works and began inserting various areas from my project into the fresh project as a way of hopefully identifying the issue. Session parameters, events, migrations, factories, models, were not the issue as it continued to work in the fresh project.
One thing I noticed is that the generic routes are not accepted in my project within he test cases. I have to insert 'https://realtor.host' in front of every test route (e.g. $response = $this->get('https://realtor.host/register');
I was curious if it was not evaluating the livewire route and I tried to add my domain into the vendor's livewire component in which the test still failed and that did not cause it to work.
Any ideas on where else I can look?

Load Symfony (5.2) config from database

I am a newbie in Symfony but I know how to use OOP in PHP.
I try (with frustration) to couple custom parameters with Symfony configs by using Doctrine entities.
To solve the problem I used for e.g. the answer from Michael Sivolobov: https://stackoverflow.com/a/28726681/2114615 and other sources.
My solution:
Step 1: Create new package in config folder
-> config
-> packages
-> project
-> services.yaml
-> project
-> src
-> ParameterLoaderBundle.php
-> DependencyInjection
-> Compiler
-> ParameterLoaderPass.php
Step 2: Import the new resource
# config/services.yaml
...
imports:
- { resource: 'packages/project/config/services.yaml' }
...
Step 3: Package coding
# packages/project/config/services.yaml
services:
Project\:
resource: "../src"
<?php
namespace Project;
use Project\DependencyInjection\Compiler\ParameterLoaderPass;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class ParameterLoaderBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new ParameterLoaderPass(), PassConfig::TYPE_AFTER_REMOVING);
}
}
<?php
namespace Project\DependencyInjection\Compiler;
use App\Entity\SettingCategory;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class ParameterLoaderPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$em = $container->get('doctrine.orm.default_entity_manager');
$setting = $em->getRepository(SettingCategory::class)->findAll();
$container->setParameter('test', $setting);
}
}
After at all I test the new Parameter in my API controller:
$this->getParameter('Test');
But the following error message appears:
The parameter \"test\" must be defined.
Couple of things going on here. First off, loading config from a database is very unusual in Symfony so it is not surprising that you are having difficulty. Secondly, your process code is never getting called. Part of debugging is making sure that code that you expect to be called is in fact being called. Third, you really got off on a tangent with attempting to add a bundle under config. Way back in Symfony 2 there used to be more bundle related stuff under app/config and it may be that you discovered some old articles and misunderstood them.
But, the big problem here is that Symfony has what is known as a 'compile' phase which basically processes all the configuration and caches it. Hence the CompilerPassInterface. Unfortunately, services themselves are not available during the compile phase. They simply don't exist yet so no entity manager. You need to open your own database connection if you really want to load config from a database. You will want to use just a database connection object and not the entity manager since part of the compile phase is to process the entities themselves.
So get rid of all your code and just adjust your Kernel class:
# src/Kernel.php
class Kernel extends BaseKernel implements CompilerPassInterface
{
use MicroKernelTrait;
public function process(ContainerBuilder $container)
{
$url = $_ENV['DATABASE_URL'];
$conn = DriverManager::getConnection(['url' => $url]);
$settings = $conn->executeQuery('SELECT * FROM settings')->fetchAllAssociative();
$container->setParameter('test',$settings);
}
And be aware that even if you get all this working, you will need to manually rebuild the Symfony cache after updating your settings table. It is not going to be automatic. You really might consider taking a completely different approach.

Getting a list of tagged services in my controller

What i want is to add services to the service container that i want to use later in my controller or service.
So i created two services with my custom tag fbeen.admin
here they are:
services:
app.test:
class: AppBundle\Admin\TestAdmin
tags:
- { name: fbeen.admin }
fbeen.admin.test:
class: Fbeen\AdminBundle\Admin\TestAdmin
tags:
- { name: fbeen.admin }
Now i want to use all the services with the tag fbeen.admin in my controller but i dont know how.
I followed the How to work with service tags tutorial but i get stuck on this rule:
$definition->addMethodCall('addTransport', array(new Reference($id)));
On some way the addTransport method of the TransportChain class should be called but it seems that it isn't been called.
And even if it would be called then i still do not have a list of services with the fbeen.admin tag into my controller.
I am sure that i am missing something but who can explain me what it is?
p.s. I know that compilerPass runs at buildtime but for example sonata admin knows all admin classes and twig knows all twig extensions. How do they know?
Thank you for reading this :-)
Symfony 3.3
Container gets compiled once (in debug more often, but in production only once). What you manage with addMethodCall... is that once you request your service from container, which you are storing in $definition (that in this case is controller). Then container will call method addMethodCall('method'.. during initialising your service.
What it will look in container:
// This is pseudo content of compiled container
$service = new MyController();
// This is what compiler pass addMethodCall will add, now its your
// responsibility to implement method addAdmin to store admins in for
// example class variable. This is as well way which sonata is using
$service->addAdmin(new AppBundle\Admin\TestAdmin());
$service->addAdmin(new AppBundle\Admin\TestAdmin());
return $service; // So you get fully initialized service
Symfony 3.4+
What you can do is this:
// Your services.yaml
services:
App/MyController/WantToInjectSerivcesController:
arguments:
$admins: !tagged fbeen.admin
// Your controller
class WantToInjectSerivcesController {
public function __construct(iterable $admins) {
foreach ($admins as $admin) {
// you hot your services here
}
}
}
Bonus autotagging of your services. Lets say all your controllers implements interface AdminInterface.
// In your extension where you building container or your kernel build method
$container->registerForAutoconfiguration(AdminInterface::class)->addTag('fbeen.admin');
This will tag automatically all services which implement your interface with tag. So you don't need to set tag explicitly.
The thing to note here is this: The CompilerPass doesn't run the 'addTransport' (or whatever you may call it) in the compiler-pass itself - just says 'when the time is right - run $definition->addTransport(...) class, with this data'. The place to look for where that happens is in your cache directory (grep -R TransportChain var/cache/), where it sets up the $transportChain->addTransport(...).
When you come to use that service for the first time - only then is the data filled in as the class is being instantiated from the container.
This worked for me:
extend the TransportChain class with a getTransports method:
public function getTransports()
{
return $this->transports;
}
and use the TransportChain service in my controller:
use AppBundle\Mail\TransportChain;
$transportChain = $this->get(TransportChain::class);
$transports = $transportChain->getTransports();
// $transports is now an array with all the tagged services
Thank you Alister Bulman for pushing me forwards :-)

Symfony2 docs on Routing component?

I was looking for Routing Component's documentation, explicitly which types of parameters it accepts.
For example, the type: annotations in my just created app's routing.yml made me want to look what other types are out there, but there is no docs on it. I can only find the documentation in the Book and a little bit in Components.
Loader types
Main types of routing loaders are described in the component's docs. It mentions quite a few loaders:
YamlFileLoader
XmlFileLoader
PhpFileLoader
ClosureLoader
AnnotationFileLoader, AnnotationClassLoader & AnnotationDirectoryLoader
You'll find all the core loaders in the Symfony\Component\Routing\Loader namespace
It's all based on Config's component loaders, so it's worth if you also read about the Config component.
Each loader's supports() method will tell you in which circumstances the loader is actually used. For example, for the YamlFileLoader it's:
public function supports($resource, $type = null)
{
return is_string($resource)
&& 'yml' === pathinfo($resource, PATHINFO_EXTENSION)
&& (!$type || 'yaml' === $type);
}
You can see it looks at resource's extension and type.
Custom loaders
You can implement your own loaders by implementing the Symfony\Component\Config\Loader\LoaderInterface.
Read more about it in the How to Create a custom Route Loader cookbook. It actually explains quite a lot on how routing loaders work. Have a look at some 3rd party loaders too, such as the FOSRestBundle's one.
How to wire it all together
Have a look at the generated container in the Symfony Standard Edition to see how the full stack framework wires it all together. It should look similar to:
/**
* Gets the 'routing.loader' service.
*
* This service is shared.
* This method always returns the same instance of the service.
*
* #return \Symfony\Bundle\FrameworkBundle\Routing\DelegatingLoader A Symfony\Bundle\FrameworkBundle\Routing\DelegatingLoader instance.
*/
protected function getRouting_LoaderService()
{
$a = $this->get('file_locator');
$b = $this->get('annotation_reader');
$c = new \Sensio\Bundle\FrameworkExtraBundle\Routing\AnnotatedRouteControllerLoader($b);
$d = new \Symfony\Component\Config\Loader\LoaderResolver();
$d->addLoader(new \Symfony\Component\Routing\Loader\XmlFileLoader($a));
$d->addLoader(new \Symfony\Component\Routing\Loader\YamlFileLoader($a));
$d->addLoader(new \Symfony\Component\Routing\Loader\PhpFileLoader($a));
$d->addLoader(new \Symfony\Component\Routing\Loader\AnnotationDirectoryLoader($a, $c));
$d->addLoader(new \Symfony\Component\Routing\Loader\AnnotationFileLoader($a, $c));
$d->addLoader($c);
return $this->services['routing.loader'] = new \Symfony\Bundle\FrameworkBundle\Routing\DelegatingLoader($this->get('controller_name_converter'), $this->get('monolog.logger.router', ContainerInterface::NULL_ON_INVALID_REFERENCE), $d);
}
The key here is the LoaderResolver which takes care of finding an appropriate loader for a type of configuration.

How to use third party api in symfony2

basically I am trying to integrate the thrid party api in symfony2 application. I have the php class from api documention. I want integrate that in my custom bundle controller.
I also looking to add the option to configure the api key and secrets. I don't know how to start. Please look at below that i want to do
Example:-
namespace Acme\DemoBundle\Controller;
class DemoController extends Controller
{
public function indexAction()
{
$myApi = new MyApi($key,$secret); // this is the stuff, I am trying to do!
return array();
}
}
First I try to create the library format symfony package. I don't know how to achive this this type. Can Some one tell me what are thinks I need to do.
TO do this in a proper way, you have to declare you api class as a service & use dependancy injection to inject your parameters :
parameters:
your_api.class: Acme\HelloBundle\Lib\YourApiClass
your_api.key: "key value for your api"
your_api.token: "token value for your api"
services:
yourApiService:
class: "%your_api.class%"
arguments: ["%your_api.key%", "%your_api.token%"]
And in your controller & many other places you'll have access like that :
$api = $this->get('yourApiService');
Take a look for more informations : http://symfony.com/doc/current/book/service_container.html
you can use it as a service:
http://symfony.com/doc/current/book/service_container.html
when i try to implement an extern api, i think about adapter pattern:
http://en.wikipedia.org/wiki/Adapter_pattern

Resources