Process Isolation in PHPUnit

Published in PHP, PHPUnit, Testing on Aug 19, 2010

I was recently writing a unit test for an autoloader when I came across a somewhat unintuitive behavior in PHPUnit.

One requirement of the test suite was that some test methods had to be run in a separate process since class declarations reside in the global scope and persist until the process terminates. So, I slapped a @runInSeparateProcess annotation in the docblock of a test method with that requirement, ran the test suite... and watched that test method fail because the class was still being declared.

It took some head-scratching and tracing through the source of PHPUnit itself to figure out what was going on. When you run the phpunit executable, it's actually instantiating PHPUnit_TextUI_TestRunner. The eventual result of this is that the run() method inherited by your subclass of PHPUnit_Framework_TestCase is called.

Depending on the value of the also-inherited $preserveGlobalState instance property, which can be set via the setPreserveGlobalState() method, multiple measures are undertaken to preserve the state of the current process. One such measure is including files for all the classes currently defined in that process, which is what was tripping me up because $preserveGlobalState has a default value of true. $preserveGlobalState must contain its intended value before the run() method is called. The easiest way that I've found to facilitate this is to override the run() method in your subclass, call setPreserveGlobalState() there, then call the parent class implementation of run(). I've included a code sample below to illustrate this.

class MyTestCase extends PHPUnit_Framework_TestCase
{
    public function run(PHPUnit_Framework_TestResult $result = NULL)
    {
        $this->setPreserveGlobalState(false);
        return parent::run($result);
    }
}

So, if you try to use the @runInSeparateProcess or @runTestsInSeparateProcesses annotations that PHPUnit offers, be aware that the global state will be preserved by default. You will need to explicitly set it to not be so if running tests in separate processes is to have the effect that you are probably intending.

Update 2011-01-14: @preserveGlobalState enabled|disabled can be used in place of what I did in the PHP code itself above. Thanks to Sebastian Bergmann for pointing this out.

Update 2021-05-21: preserveGlobalState has been changed to be disabled by default when runInSeparateProcess is active on the master branch, so this should take effect in a near-future release.