12

i would write a test for Symfony2 with FOSUserBundle.

At the moment i tried some ways and no one works.

I need a function like "createAuthClient".

Here is my basic class. I post it because you could understand my problem better.

<?php
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\BrowserKit\Cookie;

class WebTestMain extends WebTestCase
{
    protected static $container;

    static protected function createClient(array $options = array(), array $server = array())
    {
        $client = parent::createClient($options, $server);

        self::$container = self::$kernel->getContainer();

        return $client;
    }

    static function createAuthClient(array $options = array(), array $server = array())
    {
        // see lines below with my tries
    }
}

First try:

if(static::$kernel === null)
{
    static::$kernel = static::createKernel($options);
    static::$kernel->boot();
}
if(static::$container === null)
{
    self::$container = self::$kernel->getContainer();
}

$parameters = self::$container->getParameter('test');
$server = array_merge(array('HTTP_HOST' => $parameters['host'], 'HTTP_USER_AGENT' => $parameters['useragent']), $server);

$client = self::createClient($options, $server);

$userProvider = self::$container->get('fos_user.user_manager');
$user = $userProvider->findUserBy(array('id' => 1));
$client->getCookieJar()->set(new Cookie('MOCKSESSID', true));
$session = self::$kernel->getContainer()->get('session');
$token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
$client->getContainer()->get('security.context')->setToken($token);
$session->set('_security_main', serialize($token));

return $client;

Then i searched and searched... My second try.

if(static::$kernel === null)
{
    static::$kernel = static::createKernel($options);
    static::$kernel->boot();
}
if(static::$container === null)
{
    self::$container = self::$kernel->getContainer();
}

$parameters = self::$container->getParameter('test');
$server = array_merge(
    array(
        'HTTP_HOST' => $parameters['host'],
        'HTTP_USER_AGENT' => $parameters['useragent'],
        'PHP_AUTH_USER' => 'admin',
        'PHP_AUTH_PW'   => 'admin'
    ),
    $server
);
$client = static::createClient(array(), array());
$client->followRedirects();
return $client;

And here is my last try before i post this question...

$client = self::createClient($options, $server);

$parameters = self::$container->getParameter('test');
$server = array_merge(
    array(
        'HTTP_HOST' => $parameters['host'],
        'HTTP_USER_AGENT' => $parameters['useragent']
    ),
    $server
);

$client->setServerParameters($server);

$usermanager = self::$container->get('fos_user.user_manager');
$testuser = $usermanager->createUser();
$testuser->setUsername('test');
$testuser->setEmail('test@mail.org');
$testuser->setPlainPassword('test');
$usermanager->updateUser($testuser);

return $client;

Thank you in Advance.

PatrickB
  • 3,225
  • 5
  • 31
  • 55

2 Answers2

17

The best way I have found to test with an authenticated user is to just visit your login page and submit the form with user name and password you have loaded from a fixture. This may seem slow and cumbersome but will test what the user will actually do. You can even create your own method to make using it quick and easy.

public function doLogin($username, $password) {
   $crawler = $this->client->request('GET', '/login');
   $form = $crawler->selectButton('_submit')->form(array(
       '_username'  => $username,
       '_password'  => $password,
       ));     
   $this->client->submit($form);

   $this->assertTrue($this->client->getResponse()->isRedirect());

   $crawler = $this->client->followRedirect();
}
Michael Smith
  • 409
  • 3
  • 6
  • 1
    @Michael: could you please tell me that how to verify logged in user, because if we give wrong password than redirection also happen. and $this->assertTrue($this->client->getResponse()->isRedirect()); this seems work in both – Code Oct 21 '13 at 12:58
  • @Code you could assert that response after redirection contains something you have in homepage, or it doesn't contain something that there is in the login page (see edited answer). You could also verify the url where redirection is sending you, before it happen, with the same method! – Full Dec 04 '17 at 16:16
15

Create an AbstractControllerTest and create an authorized client on setUp() as follow:

<?php
// ...
use Symfony\Component\BrowserKit\Cookie;

abstract class AbstractControllerTest extends WebTestCase
{
    /**
     * @var Client
     */
    protected $client = null;


    public function setUp()
    {
        $this->client = $this->createAuthorizedClient();
    }

    /**
     * @return Client
     */
    protected function createAuthorizedClient()
    {
        $client = static::createClient();
        $container = $client->getContainer();

        $session = $container->get('session');
        /** @var $userManager \FOS\UserBundle\Doctrine\UserManager */
        $userManager = $container->get('fos_user.user_manager');
        /** @var $loginManager \FOS\UserBundle\Security\LoginManager */
        $loginManager = $container->get('fos_user.security.login_manager');
        $firewallName = $container->getParameter('fos_user.firewall_name');

        $user = $userManager->findUserBy(array('username' => 'REPLACE_WITH_YOUR_TEST_USERNAME'));
        $loginManager->loginUser($firewallName, $user);

        // save the login token into the session and put it in a cookie
        $container->get('session')->set('_security_' . $firewallName,
            serialize($container->get('security.context')->getToken()));
        $container->get('session')->save();
        $client->getCookieJar()->set(new Cookie($session->getName(), $session->getId()));

        return $client;
    }
} 

NOTE: Please, replace the username with your test username.

Then, extends the AbstractControllerTest and use the global $client to make requests as follow:

class ControllerTest extends AbstractControllerTest
{
    public function testIndexAction()
    {
        $crawler = $this->client->request('GET', '/admin/');

        $this->assertEquals(
            Response::HTTP_OK,
            $this->client->getResponse()->getStatusCode()
        );
    }
}

This method tested and works fine

A.L
  • 10,259
  • 10
  • 67
  • 98
DeadManSpirit
  • 2,036
  • 2
  • 20
  • 27
  • @k0pernikus I am not using any namespace in my example, so it is using global namespace implicitly which does not require use statment. – DeadManSpirit Dec 16 '14 at 05:54
  • 1
    I find that making dependencies explicit beats implicit convention many times. (The fact that I'm writing this comment should make this obvious.) In my current project I have a bunch of `Client` classes from different third party bundles. I had to look and test until realising that `Symfony\Component\BrowserKit\Client` was the correct one. Your answer, while providing a nice working example would be much more useful if it included use statements. As of now, manual work in figuring out the solution is still required. – k0pernikus Dec 16 '14 at 14:57
  • 1
    @Paolo Stefan: If it's not working it's because of an open issue in mocked session storage in Symfony 2.6 and higher: https://github.com/symfony/symfony/issues/13450. After upgrading, we had to switch to Michael Smith's answer (using client/crawler to log in, following redirects, etc). – okdewit Jul 07 '16 at 07:57
  • This technique looks nice but doesn't work as of Symfony 3 (at least not as of the type I left this comment). The technique written by Michael Smith works fine. – Anton Babushkin Apr 11 '17 at 06:37