Symfony 2 - Adding session data to request object during unit testing - symfony

I'm trying to set up some testing for my REST API and I need to set a session variable inside the request object. The usual methods do not seem to work.
$session = $request->getSession();
$session->set('my_session_variable', 'myvar');

You should use WebTestCase
Then you can do things which are described in answer for similar question: how-can-i-persist-data-with-symfony2s-session-service-during-a-functional-test
so something like:
$client = static::createClient();
$container = $client->getContainer();
$session = $container->get('session');
$session->set('name', 'Sensorario');
$session->save();

If you use WebTestCase, you can retrieve the "session" service.
With this service, you can :
start the session,
set some parameters into session,
save the session,
pass the Cookie with sessionId to the request
The code can be the following :
use Symfony\Component\BrowserKit\Cookie;
....
....
public function testARequestWithSession()
{
$client = static::createClient();
$session = $client->getContainer()->get('session');
$session->start(); // optional because the ->set() method do the start
$session->set('my_session_variable', 'myvar'); // the session is started here if you do not use the ->start() method
$session->save(); // important if you want to persist the params
$client->getCookieJar()->set(new Cookie($session->getName(), $session->getId())); // important if you want that the request retrieve the session
$client->request( .... ...

A short snippet to set some value in session
$session = $this->client->getRequest()->getSession();
$session->set('name', 'Sensorario');
And a very very simple example to get this value
echo $session->get('name');

Related

Return body of Symfony Mailer email in a functional test?

With Mailer, it appears not possible to retrieve the body of an email in a functional test. For example, with
$email = $this->getMailerMessage(0);
$body = $email->getBody();
var_dump($body);
$body is revealed as
object(Symfony\Component\Mime\Part\Multipart\AlternativePart)...
There are no (apparent) accessible properties of that object. Is there any method for accessing the text of the body of an email sent with Symfony Mailer in a functional test?
With SwiftMailer one could retrieve the body of an email in a functional test with:
$mailCollector = $this->client->getProfile()->getCollector('swiftmailer');
$collectedMessages = $mailCollector->getMessages();
$message = $collectedMessages[0];
$body = $message->getBody();
Took quite a while to find this. Backtracked through assertEmailHeaderSame to find assertEmailHtmlBodyContains then class EmailHtmlBodyContains which has $message->getHtmlBody().
So now my test includes $body = $email->getHtmlBody();, which is a string. Which can be parsed.
So, let's start. At first create MOCK of you service, in your test like this:
$this->mailer = $this->createMock(\Swift_Mailer::class);
self::$container->set('swiftmailer.mailer.default', $this->mailer);
after define array variable for your messages in current test
/** #var null|\Swift_Message $emailMessage */
$emailMessages = [];
$this->mailer
->method('send')
->willReturnCallback(function (\Swift_Message $message) use (&$emailMessages) {
$emailMessages[] = $message;
});
so, we will catch each email to $emailMessages, you need just to choose needed message and assert its content like:
$emailMessage = $emailMessages[0] ?? null;
$this->assertNotNull($emailMessage);
$this->assertInstanceOf(\Swift_Message::class, $emailMessage);
$body = $emailMessage->getBody();
$expected = \file_get_contents(__DIR__ . '/../data/RestController/ServiceController/you_message_body.html');
// do not forget to replace dynamic data in the snapshot.
$body = \preg_replace('/cid:[a-z0-9]{32}/ui', 'cid_here', $body);
$this->assertEquals($expected, $body);
Create services_test.yaml near your services.yaml
should look like this:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
public: true
test.mailer_symfony: '#Symfony\Component\Mailer\MailerInterface'
In the setup method mock real service and change it in the service container
use Symfony\Component\Mailer\MailerInterface;
// setUp()
$this->mailer = $this->createMock(MailerInterface::class);
self::$container->set('test.mailer_symfony', $this->mailer);
Mock Method send() in you test, according this code https://github.com/symfony/mailer/blob/5.x/MailerInterface.php it should be something like:
use use Symfony\Component\Mime\RawMessage;
// ....
/** #var RawMessage[] $emailMessages */
$emailMessages = [];
$this->mailer
->method('send')
->willReturnCallback(function (RawMessage $message, Envelope $envelope = null) use (&$emailMessages) {
$emailMessages[] = $message;
});
that's all, you can assert $emailMessages in the end of your test, so, also you can add expects and check invocations amount of send() method

symfony crud simple request

i'm trying to do a simple add without the form generated by doctrine
$mail = new Subscription();
$request = $this->getRequest();
if ($request->getMethod() == "POST") {
$em = $this->getDoctrine()->getManager();
$samplees = $request->get("samplees");
$mail->setEmail($samplees);
$em->persist($mail);
$em->flush();
return $this->redirect($this->generateUrl('user_homepage'));
}
First of all, Doctrine2 will not handle any form facility (nor creation neither data binding process): the whole process is up to symfony and its form bundle.
That said, if you need to retrieve a posted data you need to modify
$samplees = $request->get("samplees");
into
$samplees = $request->request->get("samplees");
This because $request is the whole Request object (so, basically, it will handle also get parameters [$request->query->get(...)] just to say one of the functionalities)

CSRF Token from the controller

I have a controller getting a form posted.
public function myPostAction(Request $request)
{
$form = $this->createForm('my_form', $my_object);
$form->handleRequest($request);
#...
I can see my CSRF token posted as parameter
my_form[_token] => lH38HTm5P0Cv3TOc4-9xi2COx-cZ670mpJ_36gR8ccI
I simply need to read it
$form->get('_token')
This tells me
Child "_token" does not exist.
How can I get this token ?
Here is the workaround I'm going to use meanwhile:
$token = $request->get($form->getName())['_token'];
I also noticed by chance that the intention used to generate the token is the form name
$csrf = $this->get('form.csrf_provider');
$intention = $form->getName();
$token = $csrf->generateCsrfToken($intention);
Like #Pierre de LESPINAY said, it is possible to do it by retrieving Token Manager service.
This service can also be injected in your constructor like that :
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
...
public function __construct(CsrfTokenManagerInterface $tokenManager)
{
$this->tokenManager = $tokenManager;
}
And used later like previously demonstrated :
$token = $this->tokenManager->getToken('myformname')->getValue();
You can get it with:
$request->request->get('my_form[_token]');
If you didn't disable CSRF-protection it will be applied and validated automatically and you don't need to check it by self.

symfony2 test flashbag or session data

How do you test for FlashBag message?
Tried this:
public function testInvalidLogin()
{
$session = $this->client->getContainer()->get('session');
$crawler = $this->client->request('GET', '/login');
$this->assertTrue($this->client->getResponse()->isSuccessful());
$form = $crawler->filter('form');
$this->assertGreaterThan(0, $form->count());
$form = $form->form();
$this->assertNotEmpty($form);
$form['_username'] = 'username';
$form['_password'] = 'password';
$this->client->submit($form);
$this->assertTrue($this->client->getResponse()->isRedirect('http://localhost/login'));
$this->client->followRedirect();
$session = $this->client->getContainer()->get('session');
var_dump($session->getBag('flashes')->all()); // this print an empty array
}
The login controller is sets a flash message 'Bad credentials' but i'm not able to see it during the tests.
Probably it's because after your redirection you are poping flash message eg. somewhere in your template. Flash bag container remove flash message just after you call get method (to be specified - removing is implemented IN get method...). If you want just get the message without poping it you should use peek method.
I guess that if you move var_dump before followRedirect then you will get the result you are expecting.

Symfony2, FOSUserBundle, authentication with cookies disabled

How I can authentication in Symfony2 without cookies in a brouser? How can generate some like this http://some.site/hello/roman?PHPSESSID=9ebca8bd62c830d3e79272b4f585ff8f or this http://some.site/9ebca8bd62c830d3e79272b4f585ff8f/hello/roman or some other url that was always available sessionid parameter. Thank you for any help.
You have to to two things. First you must extend the session storage to get the session from the query param.
namespace Elao\BackBundle\Session;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Session\Storage\NativeFileSessionStorage;
class Storage extends NativeSessionStorage
{
public function __construct($savePath = null, array $options = array(), ContainerInterface $container)
{
$request = $container->get('request');
if ($request->query->has('sessionId')) {
$request->cookies->set(session_name(), 1); // We have to simulate this cookie, in order to bypass the "hasPreviousSession" security check
session_id($request->query->get('sessionId'));
}
return parent::__construct($savePath, $options);
}
}
Source: http://www.elao.com/blog/symfony-2/symfony-2-loading-session-from-query-param.html
The next point, should be replacing the UrlGenerator to generate every url with the session id param. A example to do this, can be found in this answer.
But as nifr in the comment said, it's not a very clean requirement.

Resources