PHPStorm PHPUnit Runner and session

759 views
Skip to first unread message

fiveDust

unread,
Oct 25, 2012, 6:18:45 AM10/25/12
to pha...@googlegroups.com
Looking at the devtools I can see some focus on PHPStorm, which I use for coding. 
I like the PHPUnit Test runner that comes with PHPStorm and I never had problems using it testing the MVC flow of my Zend FW applications in respect of sessions. I am running into some problems now trying to test Phalcon that way and I can't figure out what is going on. For example:

public function test_session()
    {
        $session = $this->getSession(); // session is set via DI and retrieved via getShared()
        $this->assertInstanceOf( '\Phalcon\Session', $session ); // PASSES
        $session->set( 'unitesttest', array( 'prop' => 'test' ) );
        $session->set( 'unitesttest2', 'test' ) );
        $sessArr = $session->get('unittesttest');
        $this->assertInternalType( 'array', $sessArr ); // FAILS because $sessArr is null
        $this->assertEquals( 'test',  $session->get('unittesttest2') ); // FAILS -> is null

    }



Any thoughts anybody?



Message has been deleted

fiveDust

unread,
Oct 25, 2012, 6:48:21 AM10/25/12
to pha...@googlegroups.com
Just to add - executing the test via PHPUnit TestRunner in the browser passes the tests. 

Andres-Gutierrez

unread,
Oct 25, 2012, 2:44:47 PM10/25/12
to pha...@googlegroups.com
I think this is caused because PHPUnit is producing some kind of output (sending the response headers), $session->start() doesn't start the session if the headers were sent, you may force to start the session calling @session_start() instead of $session->start() they will produce the same result

2012/10/25 fiveDust <henry.s...@gmail.com>



--
You received this message because you are subscribed to the Google Groups "Phalcon PHP Framework" group.
To post to this group, send email to pha...@googlegroups.com.
To unsubscribe from this group, send email to phalcon+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msg/phalcon/-/04Cngp1xwA8J.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

fiveDust

unread,
Oct 26, 2012, 7:39:53 AM10/26/12
to pha...@googlegroups.com, andres.g...@phalconphp.com
PHPStorm's PHPUnit Runner is using the cli. error outputs and the like are supressed ( logged to file by error handler ). 

I make sure the session is only started once ( in the test bootstrap, a singular di instance is used by setting/getting \Phalcon\DI\FactoryDefault::getDefault() ):

if( !$di->has( 'session' ) ) {
            $di->set( 'session', function () {
                $session = new \Phalcon\Session\Adapter\Files();
                $session->start();
                return $session;
            } );
        }


Now this is completely weird. Look at the following tests. When I access the $_SESSION array directly first, subsequent writes through Phalcon Session pass, otherway around makes it fail. $session->isStarted() fails in every case.


 public function test1()
    {
        $session = $this->_di->getShared('session');

         $_SESSION['test'] = 'test';
        $this->assertEquals( 'test', $_SESSION['test'] ); // PASSES
        $this->assertEquals( 'test', $session->get('test') ); // PASSES


        $session->set( 'test2', 'test2' );
        $this->assertEquals( 'test2',  $_SESSION['test2'] ); // PASSES
        $this->assertEquals( 'test2',  $session->get('test2') ); // PASSES


        $this->assertTrue( $session->isStarted(), "session 1 not started" ); // FAILS
    }


    public function test2()
    {
        $session = $this->_di->getShared('session');

        $session->set( 'test2', 'test2' );
        $this->assertEquals( 'test2',  $_SESSION['test2'] ); // FAILS -> is null
        $this->assertEquals( 'test2',  $session->get('test2') ); // FAILS -> is null

         $_SESSION['test'] = 'test';
         $this->assertEquals( 'test', $_SESSION['test'] ); // PASSES
         $this->assertEquals( 'test', $session->get('test') ); // PASSES

         $this->assertTrue( $session->isStarted(), "session 1 not started" ); // FAILS
    }


I am really confused by this. Any further thoughts as this breaks the entire test flow here.

fiveDust

unread,
Oct 26, 2012, 8:03:34 AM10/26/12
to pha...@googlegroups.com, andres.g...@phalconphp.com
Basically, when I now replace all $session->set( $key, $value ) with $_SESSION[ $key, $value ] , all works fine. 
For now I have no choice than replacing the Phalcon Session write with the native access. In my Auth Framework I am working on I make a switch via a static var in the Auth Facade, bool $isUnitTestEnabled. 

Still would be good to know what is causing the problem and what implications directly accessing $_SESSION instead $session->set  has.

Andres-Gutierrez

unread,
Oct 27, 2012, 1:06:40 PM10/27/12
to fiveDust, pha...@googlegroups.com
Please change $session->start() by session_start()

2012/10/26 fiveDust <henry.s...@gmail.com>

fiveDust

unread,
Oct 27, 2012, 2:32:40 PM10/27/12
to pha...@googlegroups.com, fiveDust, andres.g...@phalconphp.com
I tried using session_start() as you suggested earlier, but this doesn't work either.


$di = new \Phalcon\DI\FactoryDefault();
\Phalcon\DI\FactoryDefault::setDefault( $di ); 

if( !$di->has( 'session' ) ) {
            $di->set( 'session', function () {
                $session = new \Phalcon\Session\Adapter\Files();
                session_start();
                return $session;
            } );

Wojtek Gancarczyk

unread,
Oct 28, 2012, 5:43:38 AM10/28/12
to pha...@googlegroups.com, fiveDust, andres.g...@phalconphp.com
I don't know if I'm following the whole problem with session correctly but here are couple ideas:

* I think your approach with $_SESSION variable isn't safe. Every time I assign a variable to a $_SESSION key I can access it without any problems. PHP won't even yield that you haven't started a session yet which leads to a fact that assigning a value to a session key does not confirm your session is working properly.
* I have looked at ZF1 and ZF2 session tests. As far as I can tell it seems they both use some kind of work around to mock the lack of proper session in test/cli environment.
* After all session is "just" a storage. Many suggest, you should write a wrapper around it and use mocks in test context to check read/write access. Phalcon should probably expose API for telling session that the application is currently in the test environment and should behave accordingly. You can work out a NFR for that.
* You are definitely right, this code should work in cli:


$session = new \Phalcon\Session\Adapter\Files();
$session->set( 'test', 'test' );
echo $session->get('test'); // Should return test

It might have something to do with writing the variables into session though. I have seen code, where session writing was done in functions registered through register_shutdown_function and during runtime session values were just object members. Maybe phalcon developers can give us some more insight how reading/writing is handled internally?

fiveDust

unread,
Oct 28, 2012, 7:09:30 AM10/28/12
to pha...@googlegroups.com, fiveDust, andres.g...@phalconphp.com
That was useful Wojtek! I would like to wait what the Phalcon Devs have to say on that. Then I would follow the examples of Zend or Laravel to work out a NFR. 

Andres-Gutierrez

unread,
Oct 28, 2012, 2:11:38 PM10/28/12
to pha...@googlegroups.com, fiveDust
Actually, this code works in CLI:

<?php


$session = new Phalcon\Session\Adapter\Files();
$session->start();
$session->set('test', 'test');
echo $session->get('test'); // Should return test

But, as I explained before, PHPUnit produces some output before the session start, so due to the "headers are already sent", Phalcon didn't start the session, for that reason you must change $session->start() by @session_start()

2012/10/28 Wojtek Gancarczyk <ganca...@gmail.com>
To view this discussion on the web visit https://groups.google.com/d/msg/phalcon/-/GScCJf1MZ-AJ.

fiveDust

unread,
Oct 29, 2012, 2:32:28 PM10/29/12
to pha...@googlegroups.com, fiveDust, andres.g...@phalconphp.com
Thans Andres. As I wrote earlier I tried @session_start()  but without effect. session_start() further lets session->isStarted() fail, which would cause more problems in my tests.

Also PHPUnit produces a notice in case the headers have been sent which I can detect : A session had already been started - ignoring session_start()  ... This error doesnt occur in my setup thanks to the static DI default. I would see it in my console logs otherwise.


Now after playing a bit the error seems to relate to di->getShared() after the context of one test method has finished. The first test always passes - any subsequent test trying to assert session->get() fails. 


Have a look at the following 2 cases : 


Avoiding DI completely -> all tests pass : 

class Session1Test extends PHPUnit_Framework_TestCase
{

    private static $session;

    public static function setUpBeforeClass()
    {
        static::$session = new \Phalcon\Session\Adapter\Files();
        static::$session->start();
    }

    public function test_one()
    {
        $session = static::$session;
        $this->assertTrue( $session->isStarted() ); // PASSES

        $session->set( 'test', 'value' );
        $value = $session->get('test');
        $this->assertEquals( 'value', $value );  // PASSES

        $session->set( 'test2', 'value' );
        $value = $session->get('test2'); 
        $this->assertEquals( 'value', $value );  // PASSES
    }


    public function test_two()
    {
         $session = static::$session;
        $this->assertInstanceOf( '\Phalcon\Session\Adapter\Files', $session );
        $session->set( 'test3', 'value3' );
        $value = $session->get( 'test3' );
        $this->assertEquals( 'value3', $value ); // PASSES
    }
}





Now this case uses DI and the second test ( and any subsquent using session->get() ) fails

class Session2Test extends PHPUnit_Framework_TestCase
{
    protected $_di;

    public static function setUpBeforeClass()
    {
        $di = new \Phalcon\DI();
        \Phalcon\DI::setDefault( $di );

        $di->set( 'session', function () {
            $session = new \Phalcon\Session\Adapter\Files();
            $session->start();
            return $session;

        } );

  
    }

    public function setUp()
    {
        $this->_di =  \Phalcon\DI::getDefault();
    }

    public function test_one()
    {
        $session = $this->_di->getShared('session');

        $this->assertTrue( $session->isStarted() ); // PASSES

        $session->set( 'test', 'value' );
        $value = $session->get('test');
        $this->assertEquals( 'value', $value );  // PASSES

        $session->set( 'test2', 'value2' );
        $value = $session->get('test2');
        $this->assertEquals( 'value2', $value );  // PASSES

    }


    public function test_two()
    {
        $session = $this->_di->getShared('session');

        $this->assertTrue( $session->isStarted() );
        $session->set( 'test3', 'value3' );
        $value = $session->get( 'test3' );
        $this->assertEquals( 'value3', $value ); // Failed asserting that null matches expected 'value3'.
    }
}


This is very confusing. Has anybody an explanation for it?

fiveDust

unread,
Oct 29, 2012, 4:22:09 PM10/29/12
to pha...@googlegroups.com, fiveDust, andres.g...@phalconphp.com
Ok, I had to do an explicit instantiation in setupBeforeClass for some reason. This works now : 

  public static function setUpBeforeClass()
    {
        $di = new \Phalcon\DI();
        \Phalcon\DI::setDefault( $di );

        $di->set( 'session', function () {
            $session = new \Phalcon\Session\Adapter\Files();
            return $session;

        } );

       $di->getShared('session')->start(); // THIS IS NECESSARY TO MAKE IT WORK
        
    }


Must be PHPStorm's PHPUnit adapter script.


Wojtek Gancarczyk

unread,
Oct 30, 2012, 6:25:15 AM10/30/12
to pha...@googlegroups.com, fiveDust, andres.g...@phalconphp.com
I was playing around with it too and here are my results:

* setUp and tearDown methods will be executed before and after each test respectively. To avoid headers already sent warning I took the bootstrap approach. That's what I have placed in my bootstrap.php

$di = new \Phalcon\DI();

$di->set( 'session', function () {
$session = new \Phalcon\Session\Adapter\Files();
        // session_start() did not work
$session->start();
return $session;
} );

\Phalcon\DI::setDefault($di);

and in my test:

protected $_di;

public function setUp()
{
    $this->_di =  \Phalcon\DI::getDefault();
}

public function tearDown()
{
    $this->_di = null;
}

and I executed it with:

$ phpunit --bootstrap bootstrap.php SessionTest.php

First thing I have encountered was this error message:

Exception: Serialization of 'Closure' is not allowed

After researching a little bit you need to add following to your test class:

/**
 * @backupGlobals disabled
 */
class SessionTest extends PHPUnit_Framework_TestCase

After executing it again your test_one and test_two did not pass. Bottom line is, it is not the correct approach. We would have to look into PHPUnit internals and see how a single test is isolated and why phalcon is not starting a session.

* I then took your approach from your last comment and added just that at the end of bootstrap file:

$di->getShared('session');

Now all test are passing. It is definitely a strange behavior.

I thought actually it might have had something to do with serialization and backing up the globals but it wasn't the case. Maybe output buffering? I will try to research it a little bit further when I find some time.

Henry Schmieder

unread,
Oct 30, 2012, 11:33:46 AM10/30/12
to pha...@googlegroups.com, fiveDust, andres.g...@phalconphp.com
I thought it might have to do with PHPStorm's ide-phpunit.php, but now as you reproduce this, I am thinking about output buffering too. Will check as well, when i have some time to get back on this

In the meantime, this is the stack in case of the error : ( case in Session2Test of my earlier reply above )

#0 pear\PHPUnit\Framework\Assert.php(2100): PHPUnit_Framework_Constraint_IsEqual->evaluate(NULL, '')
#1 pear\PHPUnit\Framework\Assert.php(441): PHPUnit_Framework_Assert::assertThat(NULL, Object(PHPUnit_Framework_Constraint_IsEqual), '')
#2 Session2Test.php(61): PHPUnit_Framework_Assert::assertEquals('value3', NULL)
#3 [internal function]: Session2Test->test_two()
#4 pear\PHPUnit\Framework\TestCase.php(942): ReflectionMethod->invokeArgs(Object(Session2Test), Array)
#5 pear\PHPUnit\Framework\TestCase.php(804): PHPUnit_Framework_TestCase->runTest()
#6 pear\PHPUnit\Framework\TestResult.php(649): PHPUnit_Framework_TestCase->runBare()
#7 pear\PHPUnit\Framework\TestCase.php(751): PHPUnit_Framework_TestResult->run(Object(Session2Test))
#8 pear\PHPUnit\Framework\TestSuite.php(772): PHPUnit_Framework_TestCase->run(Object(PHPUnit_Framework_TestResult))
#9 pear\PHPUnit\Framework\TestSuite.php(745): PHPUnit_Framework_TestSuite->runTest(Object(Session2Test), Object(PHPUnit_Framework_TestResult))
#10 pear\PHPUnit\TextUI\TestRunner.php(325): PHPUnit_Framework_TestSuite->run(Object(PHPUnit_Framework_TestResult), false, Array, Array, false)
#11 pear\PHPUnit\TextUI\Command.php(192): PHPUnit_TextUI_TestRunner->doRun(Object(PHPUnit_Framework_TestSuite), Array)
#12 AppData\Local\Temp\ide-phpunit.php(102): PHPUnit_TextUI_Command->run(Array, true)
#13 C:\Users\Henry\AppData\Local\Temp\ide-phpunit.php(442): IDE_PHPUnit_TextUI_Command::main()
#14 {main}



Wojtek Gancarczyk

unread,
Oct 30, 2012, 4:39:52 PM10/30/12
to pha...@googlegroups.com, fiveDust, andres.g...@phalconphp.com
I think I've tackled down the problem with session and have found the explanation. First of all look at the implementation of \Phalcon\Session::start() in 0.6.0 branch:

PHP_METHOD(Phalcon_Session, start){

zval *headers_sent;

PHALCON_MM_GROW();

PHALCON_INIT_VAR(headers_sent);
PHALCON_CALL_FUNC(headers_sent, "headers_sent");
if (PHALCON_IS_FALSE(headers_sent)) {
PHALCON_CALL_FUNC_NORETURN("session_start");
phalcon_update_property_bool(this_ptr, SL("_started"), 1 TSRMLS_CC);
PHALCON_MM_RESTORE();
RETURN_TRUE;
}

PHALCON_MM_RESTORE();
RETURN_FALSE;
}

Basically if headers have been already sent, session won't be started by phalcon (this is our case). As suggested before you can start it through @session_start() but this won't update the _started property and therefore this will always fail:

$this->assertTrue( $session->isStarted() );

Second of all, instantiating the DI in this method:

public static function setUpBeforeClass()
{
    $di = new \Phalcon\DI();
    $di->set( 'session', function () {
        $session = new \Phalcon\Session\Adapter\Files();
        $session->start();
        return $session;
    } );
        
    self::$_di = $di;
}

won't solve the problem either, because the headers have been already sent. That also explains why putting the code into bootstrap didn't solve the problem until this was executed:

$di->getShared('session')

Retrieving the session from the container before PHPUnit executes the first test will correctly start the session, because the headers have not been sent yet.

Right now the question is, how can we overcome this limitation? I would personally go with test doubles (http://www.phpunit.de/manual/current/en/test-doubles.html). I think this perfectly sums it up:


I hope this finally answers all the questions. Any thoughts?
Reply all
Reply to author
Forward
0 new messages