We are moving our forum in GitHub Discussions. For questions about Phalcon v3/v4 you can visit here and for Phalcon v5 here.

Phpunit and Session don't want to work together

Hi all, i've a problem testing my MVC project with phpunit (i use phpstorm as phpunit runner)

My apps use the Redis adapter as session adapter (a custom class inherited from the Redis adapter of the incubator) and works good in browser. Unfortunately when i run tests with phpunit, session became empty... simply Redis don't receive any value. I then discovered the session isn't started (isStarted in the tests return false).

I set the session in the DI that i create in setUp() function but i can't understand why it is not started.

do you have any solution?

Actually the only workaround i've found is to run php unit with the parameter --stderr . Wondering if there is a better way.

Hi Alessando -

I've tested both Redis and native sessions with PHPUnit and have both working without the stderr flag. In my unit test bootstrap file I call this right before \Phalcon\Di::setDefault( $di ):

if ( ! $di->getSession()->isStarted() ):

The service itself calls $session->start() in the DI service registration callback but manually calling it this way seems to work. I also remember calling "@session_start()" in the bootstrap and having that work too.

If none of this helps, could you post your code or a sample?

Well i moved back again the DI instantiation in the bootstrap file and there manually started the session. it simply worked and in the debug i can see the session is started.

But another problem i worked-around yesteday came up again now. The problem is that the session test continue to fail as phpunit report the DI is not an object when i call ->get("session").

Well i've investigated more on the issue, and the problem seems to be on the phpUnit execution... The first time it execute a test (eg. test1) i see DI:getDefault() return my DI with the session shared service instantiated and started... but when it execute other tests methods (eg. test2) DI:getDefault() return NULL... this is why yesterday i moved the DI initialization in the setUp method of a baseTestCase class ally my testcases inherit from...

setting backupGlobals and backupStaticAttributes in the phpunit xml config file haven't worked... on the contrart backupGlobals=true made phpunit raise serialization exceptions...

thanks for the help!

Hi Alessandro, I share your pain in trying to get PHPUnit working correctly with Phalcon. I ran into the same problems with the DI is not an object. In my unit test bootstrap I instantiate my services and then call

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

In the UnitTestCase parent class, I have the following;

protected function setUp()
    $di = DI::getDefault();
    parent::setUp( $di );
    $this->_loaded = TRUE;

This seems to work and allows me to continue to pass the default DI object into the parent's setUp. However, I cannot say for certain if this is the correct way to set up the DI with PHPUnit. I would greatly appreciate any code samples you might have or any new info you may have discovered.

Hi Mike! Thanks for your interest, I really feel better knowing i'm not the only one had pain on this subject! Your code is exactly what i have in my project and it works only on the first test method called. I investigated and when it run the first test, in the setUp DI::getDefault(); return an object, when the secont test method is called, DI::getDefault(); return null. [Edit], i solved this problem, it was code i've inserted in my parent-parent test class to workaround another issue. It is a feature-lack of the Phalcon library, and i will open a new topic on this. To be short, there is no way to reset shared services dinamically in phalcon.

The only solution that has worked was that i had when i started the topic, that's to instantiate DI in the setUp method of the parent test case class so it created a new DI for each test-method run. Obviously this solution was not compatible with the session service initialization because phpunit run all tests in the same context and when session->start is called multiple time in the same context it raise errors... so i fixed this creating a test session class that store values in a temp file and replacing the standard session class (i use the Redis adapter) with the test-session class in the test setUp method. It's not very elegant and i think it could evolve the test project in something horrible and unmaintenable, but actually it worked .

The real problem with sessions and other request-context related problem is that phpunit run all test one after the another in the same context and especially with shared services there is no a real working way to reset it all for each method without involve workarounds and some strategy that increase code complexity. (i'm one of those that think that code (especially testing code) must be kept simple) As emerged in other topics i've joined, i think the best solution to test controller's actions is actually to use acceptance test and reserve unit-test to services/library only where the developer has more control on requirements and can mock it all up in test methods. Acceptance test are slower but they are run each in their own isolated context and well emulating user behavior.

So I've spent a lot of time thinking about this stuff and I'm on the same page as you: (a) the DI services should be reset and loaded for each test, and (b) services should be "stubbed" for things like session, cookies, etc in the tests' setUp() when needed.

Nikos has a good example of loading services through a class (https://github.com/niden/phalcon-angular-harryhogfootball/blob/master/app/library/NDN/Bootstrap.php). I'm going to try to convert my boilerplate app to use this system for my unit tests and application. I can post an update here once I do if you're interested.

I'm thinking of moving my service registrations to a class that returns a \Phalcon\DI object. So the app's index.php will load this into $di, the unit tests will get a fresh copy of the DI object for each request, and the tests themselves can modify the DI object in their own setUp() method if I further need to stub any dependencies.

You can also see the same loader/bootstrap mechanism in the website repo (which powers the main website)