2

A similar question has been asked several times here

PHPUnit triggers a new fatal error

Fatal error: Cannot redeclare class Validator in /some/path/to/Validator.php on line 6

The ValidatorTest class

class ValidatorTest extends PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider data_provider_rules
    */
    public function test_factory($rules)
    {
        define('DIR_SEP', DIRECTORY_SEPARATOR);
        define('SYS_DIR', '.'.DIR_SEP.'..'.DIR_SEP.'classes'); //relative path here

        require SYS_DIR.DIR_SEP.'Validator.php';

        $validator = Validator::factory();

        return $validator;
    }
}

And the Validator class

class Validator
{
    private static $validator = FALSE;

    public function __construct()
    {
    }

    public static function factory()
    {
        if (empty(self::$validator))
        {
            self::$validator = new Validator();
        }

        return self::$validator;
    }
}

The classes are trivial, there is no autoloading or include/require whatsoever. I am running the tests from seperate tests dir that contains only the ValidatorTest.php and configuration file phpunit.xml (copied from here). The tests run fine when

processIsolation="true"

The tests also work when using require_once instead of require. So my question is which (if any) and why: do I have to explicitly the processIsolation attribute to true (since default is false), use require_once (don't feel like the best solution) or refactor the code (stop using static scopes, relative paths, etc.) ?

Community
  • 1
  • 1
sitilge
  • 3,687
  • 4
  • 30
  • 56
  • you can't use an autoloading system in you env for phpunit instead of use `require`? – Matteo May 05 '15 at 10:45
  • @Matteo I am not using autoloading (at least not in php) – sitilge May 05 '15 at 10:48
  • i suggest you to try to write a simple autoload.php files and launch the test class again. PHPunit prefer autoload external to the test... (require etc). If you like i can try to write it for you. let me know – Matteo May 05 '15 at 11:02
  • Require_Once() will only load your classes one time during the execution, so if they are already included, they will not be included again. The require will load the classes each time they are accessed, which is likely causing your duplicate declarations. – Steven Scott May 05 '15 at 14:16

2 Answers2

3

You're using a dataProvider, so we'll assume that test_factory method is being invoked more than once.

In this method you have a require sentence. This is going to produce the error you're getting, inside or outside a test.

It's the same as if you write:

require SYS_DIR.DIR_SEP.'Validator.php';
require SYS_DIR.DIR_SEP.'Validator.php';

The solution is enhancing the way you setup your test:

By definition, the first 3 lines of the test should be executed only once, so put them into setUpBeforeClass:

class ValidatorTest extends PHPUnit_Framework_TestCase
{
     public static function setUpBeforeClass()
     {
         define('DIR_SEP', DIRECTORY_SEPARATOR);
         define('SYS_DIR', '.'.DIR_SEP.'..'.DIR_SEP.'classes'); //relative path here
         require SYS_DIR.DIR_SEP.'Validator.php';
     }

    /**
     * @dataProvider data_provider_rules
     */
     public function test_factory($rules)
     {
        $validator = Validator::factory();
        return $validator;
    }
}

Anyway, IMO using require_once and checking if the constants have already been defined is fine in a test.

gontrollez
  • 6,372
  • 2
  • 28
  • 36
  • so if i am not mistaken, the test_factory will be executed as many times as data provided in `dataProvider` ? If so this seems to be a legit solution - setUpBeforeClass is the missing piece. Do you know why the solution with `processIsolation` - IMHO it means something like "consider each new data provided as it would be a new process that knows nothing about the past (what classes loaded in this case)". – sitilge May 05 '15 at 15:37
  • Yup. When using dataProviders, the test is executed once for each data set. This is convenient because you can get info on what cases pass and what don't independently. Not sure what you mean with the question. Process isolation works because being distinct processes, no other class is loaded before, but that's a very bad way of solving a trivial issue. – gontrollez May 05 '15 at 15:55
2

You have to use PHPUnit annotation in top of yor test

 /**
  * @preserveGlobalState
  * @runTestsInSeparateProcesses
  */
 class ValidatorTest extends PHPUnit_Framework_TestCase
{
...
}

Alternative you can use @runInSeparateProcess on top of a specific test instead of @runTestsInSeparateProcesses.

webcodecs
  • 217
  • 2
  • 9
  • could you explain me a bit more why should I do it? – sitilge May 05 '15 at 10:21
  • With this annotations each test will start in an own process. This prevents that a class Validator still exists because the class only exist in another process – webcodecs May 05 '15 at 11:39