13

FWIW I'm using SimpleTest 1.1alpha.

I have a singleton class, and I want to write a unit test that guarantees that the class is a singleton by attempting to instantiate the class (it has a private constructor).

This obviously causes a Fatal Error:

Fatal error: Call to private FrontController::__construct()

Is there any way to "catch" that Fatal Error and report a passed test?

4
  • There is no Unit in Simple Test ;)
    – Gordon
    Commented Jan 20, 2011 at 23:25
  • @Gordon I see the pun, but I don't get it.
    – Stephen
    Commented Jan 20, 2011 at 23:26
  • 1
    Maybe this answer can explain it
    – Gordon
    Commented Jan 20, 2011 at 23:30
  • Oldschool unit test frameworks are unfit for that. Write a PHPT for that test and mingle it into a PHPUnit/SimpleTest case using a regex on the output.
    – mario
    Commented Jan 20, 2011 at 23:44

3 Answers 3

13

No. Fatal error stops the execution of the script.

And it's not really necessary to test a singleton in that way. If you insist on checking if constructor is private, you can use ReflectionClass:getConstructor()

public function testCannotInstantiateExternally()
{
    $reflection = new \ReflectionClass('\My\Namespace\MyClassName');
    $constructor = $reflection->getConstructor();
    $this->assertFalse($constructor->isPublic());
}

Another thing to consider is that Singleton classes/objects are an obstacle in TTD since they're difficult to mock.

5

Here's a complete code snippet of Mchl's answer so people don't have to go through the docs...

public function testCannotInstantiateExternally()
{
    $reflection = new \ReflectionClass('\My\Namespace\MyClassName');
    $constructor = $reflection->getConstructor();
    $this->assertFalse($constructor->isPublic());
}
3

You can use a concept like PHPUnit's process-isolation.

This means the test code will be executed in a sub process of php. This example shows how this could work.

<?php

// get the test code as string
$testcode = '<?php new '; // will cause a syntax error

// put it in a temporary file
$testfile = tmpfile();
file_put_contents($testfile, $testcode);

exec("php $tempfile", $output, $return_value);

// now you can process the scripts return value and output
// in case of an syntax error the return value is 255
switch($return_value) {
    case 0 :
        echo 'PASSED';
        break;
    default :
        echo 'FAILED ' . $output;

}

// clean up
unlink($testfile);
9
  • In practice, this only works for detecting syntax errors, because (1) a script does not usually survive isolated, (2) it's unfeasible to bootstrap an entire application like this, (3) it does not create a testing/repeatable context, (4) not having all the context set up could cause false fatal errors like undefined function. Thus, instead of executing php $tempfile it's better to execute php --no-php-ini --syntax-check $tempfile. php.net/manual/en/features.commandline.options.php
    – aercolino
    Commented Jan 15, 2015 at 19:17
  • Can you prove that? I don't think so
    – hek2mgl
    Commented Jan 15, 2015 at 21:12
  • Well, it "works" in the console because I see the fatal error. The $return is always 255 for the six uncatchable errors and always 0 otherwise. I think I need a shutdown handler to get to the error code. -- as for PHPUnit, even if I @runInSeparateProcess a single test causing a fatal error, it always appears as an 'E'. -- Your idea is interesting, and I upped you before. But to make it work, I think I need to dig into PHPUnit and write a patch or plugin. I wonder why nobody did it before. Is it unreasonable to expect a script to fail?
    – aercolino
    Commented Jan 20, 2015 at 23:44
  • Now I got your concerns. Will play around with PHPUnit a little bit, probably write some code and give you a feedback..
    – hek2mgl
    Commented Jan 22, 2015 at 11:09
  • I've found there is a customizable isolation template. There we need to register_shutdown_function('__phpunit_shutdown', $test, $result). __phpunit_shutdown($test, $result) (in case of error) only prints a serialized array like usual, but with an added error key set to error_get_last(). Then we can add support for an @expectedShutdownError <code> from PHPUnit_Util_PHP::runTestJob, which would call processChildResult with doctored arguments (mainly stderr = '').
    – aercolino
    Commented Jan 22, 2015 at 17:08

Not the answer you're looking for? Browse other questions tagged or ask your own question.