I'm testing some legacy code that extends the default php exception object. This code prints out a custom HTML error message.
I would like to mock this exception object in such a way that when the tested code generates an exception it will just echo the basic message instead of giving me the whole HTML message.
I cannot figure out a way to do this. It seems like you can test for explicit exceptions, but you can't change in a general way the behavior of an exception, and you also can't mock up an object that extends a default php functionality. ( can't think of another example of this beyond exceptions... but it would seem to be the case )
I guess the problem is, where would you attach the mocked object?? It seems like you can't interfere with 'throw new' and this is the place that the object method is called....
Or if you could somehow use the existing phpunit exception functionality to change the exception behavior the way you want, in a general way for all your code... but this seems like it would be hacky and bad....
EDIT: here is some code to make things clearer:
class FooTest extends PHPUnit_Framework_TestCase{
public function testBar(){
include '/path/to/file.php'; //generates exception
$this->assertTrue($baz);
}
}
...
//overridden exception class
class Foo_Exception extends ErrorException{
...
so, my question, is there a way to deal with this overriden class, without doing it on a case by case basis? what if I'm not testing the behavior of the exception, just the code that causes the exception?
I would first write a test that captures the exception generation behavior:
include '/path/to/file.php'; //generates exception
public function testCatchFooException() {
try {
$this->assertTrue($baz);
}
catch (Exception $expected) {
$this->assertEquals('This is expected html from exception', $expected->getMessage());
return;
}
$this->fail('An expected Exception has not been raised Foo_Excpetion.');
}
Now you can do several things with this coverage test. You can either fix up the exception, or fix the code that causes the exception.
Another thing you can do is wrap the entire file.php in a class:
class FooClass {
function runFoo() {
include '/path/to/file.php'; //generates exception
}
}
Then add tests while using extract method until you isolate exception.
[EDIT]
Here is some serious procedural legacy code:
<?php
require_once 'helper.php'; //helper file
function countNewMessages($user_id) {
}
function countNewOrders() {
}
function countNewReturns() {
}
function getDB($init = NULL) {
}
function getDisplay() {
}
getDisplay();
?>
And here is the wrapped class:
<?php
require_once ''; //helper file
class Displayer {
function countNewMessages($user_id) {
}
function countNewOrders() {
}
function countNewReturns() {
}
function getDB($init = NULL) {
}
function getDisplay() {
}
}
?>
And now I can test it:
function testGetDisplay() {
$display = new Displayer();
$this->assertEquals('html code', $display->getDisplay());
}
And test the individual functions in it. And if I can further sprout methods on it.
The above test would be considered a coverage test. There may be bugs in it, but that is what it does. So as I sprout methods the get more code coverage from tests by sprouting that I can make sure I don't break the output.
The extened PHP exception object "prints" a costum HTML error page? You mean its error message is an entire HTML page? That's not very clever...
What you can do about it is to replace the default exception handler (see this function), call getMessage on the exception and parse the HTML error page to extract the message. Then you can print the error message and kill the script. Like this (in PHP 5.3):
set_exception_handler(
function (Exception $e) {
die(parse_html_error_page($e->getMessage()));
}
);
OK, I misunderstood the question. If the script you're testing catches the error and then echoes an error page, then this has nothing to do with exceptions. You can use the ob_ family:
ob_start();
include $file;
$contents = ob_get_contents();
if (result_is_error($contents))
die(extract_error_from_result($contents));
else
echo $contents;
ob_end_clean();
Related
I'm calling a method that I know could cause an error and I'm trying to handle the error by wrapping the code in a try/catch statement...
class TestController extends Zend_Controller_Action
{
public function init()
{
// Anything here happens BEFORE the View has rendered
}
public function indexAction()
{
// Anything `echo`ed here is added to the end of the View
$model = new Application_Model_Testing('Mark', 31);
$this->view->sentence = $model->test();
$this->loadDataWhichCouldCauseError();
$this->loadView($model); // this method 'forwards' the Action onto another Controller
}
private function loadDataWhichCouldCauseError()
{
try {
$test = new Application_Model_NonExistent();
} catch (Exception $e) {
echo 'Handle the error';
}
}
private function loadView($model)
{
// Let's pretend we have loads of Models that require different Views
switch (get_class($model)) {
case 'Application_Model_Testing':
// Controller's have a `_forward` method to pass the Action onto another Controller
// The following line forwards to an `indexAction` within the `BlahController`
// It also passes some data onto the `BlahController`
$this->_forward('index', 'blah', null, array('data' => 'some data'));
break;
}
}
}
...but the problem I have is that the error isn't being handled. When viewing the application I get the following error...
( ! ) Fatal error: Class 'Application_Model_NonExistent' not found in /Library/WebServer/Documents/ZendTest/application/controllers/TestController.php on line 23
Can any one explain why this is happening and how I can get it to work?
Thanks
use
if (class_exists('Application_Model_NonExistent')) {
$test = new Application_Model_NonExistent;
} else {
echo 'class not found.';
}
like #prodigitalson said you can't catch that fatal error.
An error and an exception are not the same thing. Exceptions are thrown and meant to be caught, where errors are generally unrecoverable and triggered with http://www.php.net/manual/en/function.trigger-error.php
PHP: exceptions vs errors?
Can I try/catch a warning?
If you need to do some cleanup because of an error, you can use http://www.php.net/manual/en/function.set-error-handler.php
Thats not an exception, thats a FATAL error meaning you cant catch it like that. By definition a FATAL should not be recoverable.
Exception and Error are different things. There is an Exception class, which you are using and that $e is it's object.
You want to handle errors, check error handling in php-zend framework. But here, this is a Fatal error, you must rectify it, can not be handled.
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?
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.
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());
}
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);
so, instead of lots of instances of
if (odbc_exec($sql))
{
}
else
{
myErrorHandlingFunction();
}
I wrap that in a function
function myOdbxExec($sql)
{
if (odbc_exec($sql))
{
}
else
{
myErrorHandlingFunction();
}
}
BUT I would like myErrorHandlingFunction() to report things like __LINE__ __FILE__ etc
Which looks like I have to pass thoses infos to every call of helper functions, e.g. myOdbxExec($sql, __FILE__, __LINE__) which makes my code look messy.
function myErrorHandlingFunction($errorTExt, $fiel, $line)
{
// error reporting code goes here
}
function myOdbxExec($sql, $file, $line)
{
if (odbc_exec($sql))
{
}
else
{
myErrorHandlingFunction();
}
}
$sql = 'select * from ... blah, blah, blah...';
myOdbxExec($sql, __FILE__, __LINE__); // <==== this is *ugly*
In C I would hide it all behind a #define, e.g. #define MY_OFBC_EXEC(sql) myOdbxExec(sql, __FILE__, __LINE__)
1) (how) can I do that in PHP
2) what else is worth outoputting? e.g. error_get_last()? but that has no meaning if odbc_exec() fails ...
To rephrase the question - what's the generic approach to PHP error handling? (especially when set_error_handler() doesn't really apply?
Edit: just to be clear - I do want to handle exceptions, programming errors, etc, but, as my example shows, I also want to handle soemthings the teh PHP interpreter might noit consider to be an error, like odbc_exec() returning false().
Both the above answers mentioning debug_backtrace are answering your _______LINE_____ / _______FILE___ question with the debug_backtrace() function. I've wanted this answer too in the past, so have put together a quick function to handle it.
function myErrorHandlingFunction($message, $type=E_USER_NOTICE) {
$backtrace = debug_backtrace();
foreach($backtrace as $entry) {
if ($entry['function'] == __FUNCTION__) {
trigger_error($entry['file'] . '#' . $entry['line'] . ' ' . $message, $type);
return true;
}
}
return false;
}
You can then call, for instance, myErrorHandlingFunction('my error message');
or myErrorHandlingFunction('my fatal error message', E_USER_ERROR);
or try { ... } catch (Exception $e) { myErrorHandlingFunction($e->getMessage()); }
First off, you might want to consider using exception handling.
If for some reason, that doesn't appeal to you, you can use debug_backtrace inside your generic error handler to figure out where the handler was called from.
EDIT In response to OP's comment:
Exceptions don't have to come from PHP built-ins. You can throw your own. Since an ODBC error generally is an exceptional condition, just throw an exception when you detect one. (And catch it at some higher level).
<?PHP
if (! odbc_exec($sql) )
throw new DatabaseException('odbc_exec returned false, that bastard!');
Use debug_backtrace to figure out where a function was called from in your error handler. Whether you invoke this error handler by manually calling it, by throwing and catching exceptions, PHPs error_handler or any other method is up to the application design and doesn't really differ that much from other languages.
I can't seem to correctly do this, the error message of the exception just prints out, making the command line window harder to read. Below is how my code is structured and the test code.
public function availableFruits($fruit)
{
switch($fruit) {
case 'foo':
// all good
break;
case 'bar':
// all good
break;
default:
throw new Exception($fruit.' not available!');
break;
}
}
public function chooseFruit($fruit)
{
try {
availableFruits($fruit);
} catch (Exception $e) {
echo $e->getMessage();
}
}
public function testAvailableFruits()
{
$this->setExpectedException('Exception');
chooseFruit('Kiwi');
}
The error message will print out in the command line window like below. I tried all the methods shown in phpunit.de but same results.
..Error on line 13 in c:\file\path\fruits.php: Kiwi not available!.F
The error line prints out, how do I suppress that, am I doing it right at all?
I believe another way to do it is to add a comment block to the test method that looks like...
/**
* #expectedException ExpectedExceptionName
*/
Or, you can also catch the exception your self cause the method to fail if you don't get inside the catch block.
This is embarrassing as I found just the way to do it. Thanks Chris, but I tried that as well.
I tested the wrong method, chooseFruit isn't the method that throws the exception, so the exception error prints out:
public function testAvailableFruits()
{
$this->setExpectedException('Exception');
**chooseFruit('Kiwi');**
}
Testing the actual method that throws the exception will mute the error message, since it is not echoed at all:
public function testAvailableFruits()
{
$this->setExpectedException('Exception');
**availableFruits('Papaya')**
}
If I am correct, SimpleTest will allow you to assert a PHP error is thrown. However, I can't figure out how to use it, based on the documentation. I want to assert that the object I pass into my constructor is an instance of MyOtherObject
class Object {
public function __construct(MyOtherObject $object) {
//do something with $object
}
}
//...and in my test I have...
public function testConstruct_ExpectsAnInstanceOfMyOtherObject() {
$notAnObject = 'foobar';
$object = new Object($notAnObject);
$this->expectError($object);
}
Where am I going wrong?
Type hinting throws E_RECOVERABLE_ERROR which can be caught by SimpleTest since PHP version 5.2. The following will catch any error containing the text "must be an instance of". The constructor of PatternExpectation takes a perl regex.
public function testConstruct_ExpectsAnInstanceOfMyOtherObject() {
$notAnObject = 'foobar';
$this->expectError(new PatternExpectation("/must be an instance of/i"));
$object = new Object($notAnObject);
}
PHP has both errors and exceptions, which work slightly different. Passing a wrong type to a typehinted function will raise an exception. You have to catch that in your test case. Eg.:
public function testConstruct_ExpectsAnInstanceOfMyOtherObject() {
$notAnObject = 'foobar';
try {
$object = new Object($notAnObject);
$this->fail("Expected exception");
} catch (Exception $ex) {
$this->pass();
}
}
or simply:
public function testConstruct_ExpectsAnInstanceOfMyOtherObject() {
$this->expectException();
$notAnObject = 'foobar';
$object = new Object($notAnObject);
}
But note that this will halt the test after the line where the exception occurs.
Turns out, SimpleTest doesn't actually support this. You can't catch Fatal PHP errors in SimpleTest. Type hinting is great, except you can't test it. Type hinting throws fatal PHP errors.
you have to expect the error before it happens, then SimpleTest will swallow it and count a pass, if the test gets to the end and there is no error then it will fail. (there's expectError and expectException that act in the same way, for PHP (non-fatal) errors and Exceptions, respectively.)