Steven Lu
Steven Lu

Reputation: 2180

PHPUnit failing with Symfony2 Sessions

I'm running into an issue when trying to run a controller based unit test on a controller method that implements Sessions.

In this case, here is the controller method:

/**
 * @Route("/api/logout")
 */
public function logoutAction()
{
    $session = new Session();
    $session->clear();

    return $this->render('PassportApiBundle:Login:logout.html.twig');
}

And the functional test:

public function testLogout()
{
    $client = static::createClient();
    $crawler = $client->request('GET', '/api/logout');
    $this->assertTrue($client->getResponse()->isSuccessful());
}

The error that is produced:

Failed to start the session because headers have already been sent. (500 Internal Server Error)

I've tried placing in $this->app['session.test'] = true; into the test, but still no go. Has anyone tried resolving an issue like this to unit testing a controller that uses a session?

Upvotes: 5

Views: 8062

Answers (3)

In my case, it was enough to set

framework:
    session:
        storage_id: session.storage.mock_file

in the config_test.yml. YMMV, and I don't have that much of an idea what I'm actually doing, but it works for me.

Upvotes: 8

NansP
NansP

Reputation: 61

Here just to complete Cyprian's response.

As Sven explains and when looking at the symfony's doc http://symfony.com/doc/2.3/components/http_foundation/session_testing.html, you have to instanciate the mock session with a MockFileSessionStorage object as first constructor argument.

You need to use :

    use Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage;

And code should be :

    $sessionMock = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\Session')
        ->setMethods(array('clear'))
        ->disableOriginalConstructor()
        ->setConstructorArgs(array(new MockFileSessionStorage()))
        ->getMock();

Upvotes: 2

Cyprian
Cyprian

Reputation: 11364

First of all you should use session object from container. So your action should look more like:

/**
 * @Route("/api/logout")
 */
public function logoutAction()
{
    $session = $this->get('session');
    $session->clear();

    return $this->render('PassportApiBundle:Login:logout.html.twig');
}

And then in your test you can inject service into "client's container". So:

public function testLogout()
{
    $sessionMock = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session')
        ->setMethods(array('clear'))
        ->disableOriginalConstructor()
        ->getMock();

    // example assertion:
    $sessionMock->expects($this->once())
        ->method('clear');

    $client = static::createClient();
    $container = $client->getContainer();
    $container->set('session', $sessionMock);

    $crawler = $client->request('GET', '/api/logout');
    $this->assertTrue($client->getResponse()->isSuccessful());
}

With this code you can do everything you want with your session service. But You have to be aware two things:

  • This mock will be set ONLY for one request (if you want use it in next one, you should set up it again). It's because the client restart kernel and rebuild container between each request.
  • Session handling in Symfony 2.1 is little different than Symfony 2

edit:

I've added an assertion

Upvotes: 13

Related Questions