Functional tests in symfony3, access to container after submitting a form - php

I'm writing functional tests for my Symfony3 application. I have a test which looks like this:
public function testList()
{
$client = static::createClient();
$client->getCookieJar()->set($this->cookie);
$this->sender->method('isSuccessfull')->will($this->returnValue(true));
$container = $client->getContainer();
$container->set('app.service1', $this->object1);
$container->set('app.service2', $this->object2);
$crawler = $client->request('GET', '/list/1');
$form = $crawler->selectButton('Save')->form();
$client->submit($form);
}
Everything is good until submitting form. Kernel losing the setted container services while submitting a form. How can I these services into container also after submitting a form? Maybe there is other option to resolve my problem?

If you check the source code for Symfony\Component\HttpKernel\Client::doRequest() class you can see that it terminates the kernel which is then started again later and that's why you loose all service you created manually.
I guess you have an app which you're testing so you could add the services to its services.yml. Another way could be extending the Client class with your own and overriding getContainer() method to always add these extra services (then you'd have to update the service definition for test.client in a compile pass with your customized class).

Related

Testing Laravel controller internals with a mock

So I have this Laravel controller and I want to test it.
It is an OAuth client, so a callback is needed to finish the setup.
I want to test the OAuth callback.
The code is something like this:
public function callback(): \Illuminate\Http\RedirectResponse
{
$aCookie = request()->cookie('cookie');
$authCode = request()->get('code')
$connection = OAuthpackage::getConnection();
// Store the credentials in the database
}
To test if the credentials are stored correctly in de database I want to mock the OAuthpackage because it is fine to give it face credentials.
My first thought was to just test the controller by calling that directly. The thing with that is that I don't only need to mock the Oauthpackage but also the request class because I need to face the cookie getting in the code in the GET request.
Now I read on the internet that you probably would not want to mock the request class.
So I thought about just doing the request in the test and then seeing the output.
1 It is just the regular flow
2 I need only one mock
This is what I came up with:
public function testAppCallback()
{
$user = Auth::user();
$connectionFactory = $this->createMock(OAuthPackage::class);
$connectionFactory->method('getConnection')->willReturn(new DummyConenection());
$this->disableCookieEncryption();
$response = $this->withCookies([
'cookie' => 'http://localhost',
])->get('/oauth?code=my_auth_code');
}
The DummyClass just inherits from the real class, but this way I can test its type in the debugger. Turns out that the DummyClass is not being used.
It seems like Laravel boots up a whole new instance as soon as I make a web request and therefore forgets all about the DummyClass.
How should I go about to solve this problem?

rendering symfony/form in non-symfony application

I'm trying to rewrite a "custom framework" application to the Symfony, but I can not do everything at once, so I've divided the process into steps.
From important notes - I've already implemented the symfony/templating component and the symfony/twig-bridge component.
That's how I want to output the form in the template:
<?php echo $view['form']->form($form) ?>
As I'm doing so the following error is thrown:
Symfony\Component\Form\Exception\LogicException
No block "form" found while rendering the form.
/var/www/html/vendor/symfony/form/FormRenderer.php on line 98
To render the templates I'm using the DelegatingEngine which uses the PhpEngine and the TwigEngine.
Setting up the Twig with the \Symfony\Bridge\Twig\Extension\FormExtension is well documented, but what I'm missing is the php setup. This is how I'm doing this:
new \Symfony\Component\Form\Extension\Templating\TemplatingExtension($phpEngine, $this->csrfManager());
Could you point me what am I missing or what's wrong with my setup?
I think the simplest way would have been to install the Symfony 3.3 standard edition next to your app (pending the release of Symfony Flex).
After this, find a way to use the router of Symfony with the router of your application.
So you could have the full Symfony framework, create your form type in it and let Symfony render it :
With an ajax call
With a new Symfony Kernel in your legacy app
I've found the answer:
I was using the wrong FormRendererEngineInterface. Instead of relying on the \Symfony\Component\Form\Extension\Templating\TemplatingExtension class I've registered the form helper by myself:
$phpEngine = new PhpEngine(new TemplateNameParser(), new FilesystemLoader(realpath(__DIR__.'/../Template').'/%name%'));
$twigEngine = new TwigEngine($this->twig(), new TemplateNameParser());
$this->TemplateEngine = new DelegatingEngine(array(
$phpEngine,
$twigEngine,
));
$phpEngine->addHelpers(array(
new FormHelper(new FormRenderer($this->twigFormRendererEngine())),
));
As you can see in the TemplatingEngine:
public function __construct(PhpEngine $engine, CsrfTokenManagerInterface $csrfTokenManager = null, array $defaultThemes = array())
{
$engine->addHelpers(array(
new FormHelper(new FormRenderer(new TemplatingRendererEngine($engine, $defaultThemes), $csrfTokenManager)),
));
}
It relies on the TemplatingRendererEngine while I need the TwigRendererEngine instance, as the form templates are the twig files.
Correct me if my explanation is wrong, but the solution is working.

Testing Laravel Service Providers

I'm (we're) creating a package that acts as a core component for our future CMS and of course that package needs some unit tests.
When the package registeres, the first thing it does is set the back/frontend context like this:
class FoundationServiceProvider extends ServiceProvider
{
// ... stuff ...
public function register()
{
// Switch the context.
// Url's containing '/admin' will get the backend context
// all other urls will get the frontend context.
$this->app['build.context'] = request()->segment(1) === 'admin'
? Context::BACKEND
: Context::FRONTEND;
}
}
So when I visit the /admin url, the app('build.context') variable will be set to backend otherwise it will be set to `frontend.
To test this I've created the following test:
class ServiceProviderTest extends \TestCase
{
public function test_that_we_get_the_backend_context()
{
$this->visit('admin');
$this->assertEquals(Context::BACKEND, app('build.context'));
}
}
When I'm running the code in the browser (navigating to /admin) the context will get picked up and calling app('build.context') will return backend, but when running this test, I always get 'frontend'.
Is there something I did not notice or some incorrect code while using phpunit?
Thanks in advance
Well, this is a tricky situation. As I understand it, laravel initiates two instances of the framework when running tests - one that is running the tests and another that is being manipulated through instructions. You can see it in tests/TestCase.php file.
So in your case you are manipulating one instance, but checking the context of another (the one that did not visit /admin and is just running the tests). I don't know if there's a way to access the manipulated instance directly - there's nothing helpful in documentation on this issue.
One workaround would be to create a route just for testing purposes, something like /admin/test_context, which would output the current context, and the check it with
$this->visit('admin/test_context')->see(Context::BACKEND);
Not too elegant, but that should work. Otherwise, look around in laravel, maybe you will find some undocumented feature.

Functional Test - Mock service does not persist in service container

I am hoping someone can shed some light on this issue I am facing.
[PROBLEM]
I have mocked out doctrine.orm.default_entity_manager service in my functional unit test. I inject this into the client service container so that I do not have to hit my DB during the course of my functional test. For my test that just involve a GET request I am able to verify that the controller I am testing is using my mocked service.
However, if I attempt to do a POST request by using the crawler with a form submission my mocked service does not persist. After the initial GET request the client seems to just inject doctrine.orm.default_entity_manager service again as it needs it and not my mocked version that I set in the clients service container.
In summary, during the GET request my mocked service is being used, but during the POST request EntityManager5144076565ee8_546a8d27f194334ee012bfe64f629947b07e4919__CG__\Doctrine\ORM\EntityManager is being used.
[SEE CODE SNIPPET BELOW]
[QUESTION]
Is it possible to do what I am asking? I would like to have ALL my requests use the mocked service I defined. I want to have a functional test but avoid writing or reading from the database.
[SAMPLE CODE]
// Mocks
$entityRepository = $this
->getMockBuilder('Doctrine\ORM\EntityRepository')
->setMethods(array('findby'))->disableOriginalConstructor()
->getMock();
$entityRepository->expects($this->any())->method('findBy')
->will($this->returnValue(array()));
$em = $this->getMockBuilder('Doctrine\ORM\EntityManager')
->setMethods(
array('getRepository', 'getClassMetadata', 'flush',
'persist'))->disableOriginalConstructor()
->getMock();
$em->expects($this->any())->method('flush')
->will($this->returnValue(FALSE));
$em->expects($this->any())->method('persist')
->will($this->returnValue(FALSE));
$em->expects($this->any())->method('getRepository')
->will($this->returnValue($entityRepository));
$em->expects($this->any())->method('getClassMetadata')
->will($this->returnValue(new ClassMetadata("test")));
// Create test client.
$client = static::createClient();
// Inject entity mock into service container.
$client->getContainer()
->set('doctrine.orm.default_entity_manager', $em, 'container');
// Define request
$crawler = $client->request('GET', '/locations/types/add');
// Verify a few things
$form = $crawler->selectButton('submit')->form();
$form['location_type[title]'] = "TEST TITLE";
$form['location_type[description]'] = "TEST DESCP";
$crawler = $client->submit($form);
The issue here is that the kernel is booted after (during) every request:
protected function doRequest($request)
{
// avoid shutting down the Kernel if no request has been performed yet
// WebTestCase::createClient() boots the Kernel but do not handle a request
if ($this->hasPerformedRequest) {
$this->kernel->shutdown();
} else {
$this->hasPerformedRequest = true;
}
if ($this->profiler) {
$this->profiler = false;
$this->kernel->boot();
$this->kernel->getContainer()->get('profiler')->enable();
}
https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Client.php
So you need to replace doctrine with your mock after each request:
// Inject entity mock into service container.
$client->getContainer()
->set('doctrine.orm.default_entity_manager', $em, 'container');
// Define request
$crawler = $client->request('GET', '/locations/types/add');
// Inject entity mock into service container.
$client->getContainer()
->set('doctrine.orm.default_entity_manager', $em, 'container');
An easier way to use your mock globally would be to override the doctrine settings in config_test.yml
orm:
default_entity_manager: Acme/MyBundle/Test/MockDoctrineEM
http://symfony.com/doc/master/reference/configuration/doctrine.html

PHP Unit Testing with Zend Auth and Zend ACL

I have an application that is behind a login and utilizes zend_acl and zend_auth.
During pre-dispatch I have an ACL plugin that creates all the rules out for the ACL. I also have an Auth plugin that checks if you're logged in or not and if so if you have access to the requested resource according to the ACL.
As the application is entirely behind a login the ACL is only created if you're logged in.
Unit testing this appears to be impossible, or rather more likely I'm missing something obvious.
In my unit test setup method I simulate a successful login that returns a zend_auth instance. Tests that do pass indicate that this login was successful.
However, if I then through tests attempt to dispatch to another location, or assess if the logged in user has access to a given resource it is always rejected by the plugin as they're still not logged in. I am not sure why this is, can anyone advise?
For example this passes:
public function testLoggedIn()
{
$this->assertTrue( Zend_Auth::getInstance()->hasIdentity() );
}
This fails as it's rejected by the plugin:
public function testUserAccess()
{
$this->dispatch('/home');
$this->assertResponseCode(200);
$this->assertQueryContentContains('#nav_side');
$this->resetRequest()
->resetResponse();
}
This, I have found still seems to be redirecting back to the login page as the plugins don't know the user is logged in.
Any help much appreciated.
Here is another way of creating a stub to replace your ACL Plugin (or any plugin) during testing. Put this in your ControllerTestCase and call it in the test case setUp.
public function doLogin ()
{
// create a fake identity
$identity = new stdClass();
$identity->Username = 'PHPUnit';
Zend_Auth::getInstance()->getStorage()->write($identity);
// remove the autoloaded plugin
$front = Zend_Controller_Front::getInstance();
$front->unregisterPlugin('My_Controller_Plugin_Acl');
// create the stub for the Acl class
$mockaAcl = $this->getMock(
'My_Controller_Plugin_Acl',
array('preDispatch'),
array(),
'My_Controller_Plugin_AclMock'
);
// register the stub acl plugin in its place
$front->registerPlugin($mockAcl);
}
This way your stub preDispatch method is called instead, which will bypass your actual access control checks.
The Problem you describe happens a lot with the usage of global variables and the OOP global variable (the Singleton Pattern).
There is an article by the author of PHPUnit that describes how you can avoid that by using Dependency Injection and what other possibilities you've got and since it's very descriptive, I just suggest you to read it :) http://sebastian-bergmann.de/archives/882-Testing-Code-That-Uses-Singletons.html
As an ugly alternative (if you need a quick result) you could create a stub of Zend_Auth (describe in the link) and use the PHP 5.3 reflection API to set the Zend_Auth instance variable to your stub.
Hope that helps (as the question lived 4h without an other answer)

Categories