I have given myself a challenge to making use of testing my code as I develop it as I figured this is not only a good practice especially for a back end developer but also helps one a lot in building a scalable and extendable code in the long run.
I have a class that defines an array of supported doctrine drivers. Inside this class there is a method that creates an instance of any annotation driver that is supported by my bundle.
protected $supportedAnnotationDrivers = array(
'Doctrine\ORM\Mapping\Driver\XmlDriver',
'Doctrine\ORM\Mapping\Driver\YamlDriver',
'Doctrine\ORM\Mapping\Driver\AnnotationDriver'
);
The method that creates any instance of these drivers can be seen below:
public function getAnnotationDriver($classDriver, $entityDriver)
{
if(!in_array($classDriver, $this->supportedAnnotationDrivers)){
throw new \RuntimeException(sprintf(
"%s is not a supported Annotation Driver.", $classDriver
));
}
if('Doctrine\ORM\Mapping\Driver\AnnotationDriver' === $classDriver){
$annotationDriver = new $classDriver(new AnnotationReader(), array($entityDriver));
AnnotationRegistry::registerLoader('class_exists');
}else{
$annotationDriver = new $classDriver($entityDriver);
}
return $annotationDriver;
}
Before I go any further I have to say I have consulted PHPUnit Manual and StackOverflow, here; here; here and there , for some insights but I am getting confused all the time. and none of these links are providing me with certainty that I am going about this the right way.
Below is my test function inside my test class:
public function testUnsupportedAnnotationDriverException()
{
try{
$entityManagerService = new EntityManagerService();
//Client requests a unsupported driver
$unsupportedDriver = 'Doctrine\ORM\Mapping\Driver\StaticPHPDriver';
$actualUnsupportedException = $entityManagerService->getAnnotationDriver(
$unsupportedDriver, __DIR__ . '/../../Helper'
);
}catch(\RuntimeException $actualUnsupportedException){
return;
}
$this->fail('An expected exception has not been raised!.');
}
I am really confused when it comes to this part. In my test I am using an exception that is raised by my code which is RuntimeException and my test passes. When I change client code to throw just Exception but leave my test as is it fails and throws the exception from client code. I am happy to know that my test fails because the exception thrown in my test gives me what I would hope to get when a user passes an unsupported driver.
However, I am lacking contextualization herein of the annotation usage and the $this->fail("text/for/failer/) function when testing exceptions.
How best could I go about writing a test for this method? Is what I have done exactly how the test should behave perhaps its me not having grasp the concept behind this part of PHPUnit? Below is an output I get from PHPUnit:
Case 1:Exceptions are the same from test and client code
Luyanda.Siko#ZACT-PC301 MINGW64 /d/web/doctrine-bundle
$ vendor/bin/phpunit
PHPUnit 3.7.38 by Sebastian Bergmann.
Configuration read from D:\web\doctrine-bundle\phpunit.xml
......
Time: 1.49 seconds, Memory: 3.00Mb
OK (6 tests, 9 assertions)
Luyanda.Siko#ZACT-PC301 MINGW64 /d/web/doctrine-bundle
Case 2:Exceptions are not the same from test and client code
Luyanda.Siko#ZACT-PC301 MINGW64 /d/web/doctrine-bundle
$ vendor/bin/phpunit
PHPUnit 3.7.38 by Sebastian Bergmann.
Configuration read from D:\web\doctrine-bundle\phpunit.xml
..E...
Time: 1.59 seconds, Memory: 3.00Mb
There was 1 error:
1) PhpUnitBundle\Unit\ApplicationContext\Service\EntityManagerServiceTest::testUnsupportedAnnotationDriverException
Exception: Doctrine\ORM\Mapping\Driver\StaticPHPDriver is not a supported Annotation Driver.
D:\web\doctrine-bundle\lib\DoctrineBundle\ApplicationContext\Service\EntityManagerService.php:33
D:\web\doctrine-bundle\lib\PhpUnitBundle\Unit\ApplicationContext\Service\EntityManagerServiceTest.php:68
FAILURES!
Tests: 6, Assertions: 9, Errors: 1.
I really want to know the best way to know what I am doing here and if I am doing it right - if not the best way to do it. Perpahs what I am trying to do is not even worthe it but I believe no single line of code should be seen as worthless and unworthy of testing.
In general, code that you provided is valid and work as it should. You describe what you expects from code and then test that your code meet your expectations.
In your case you are expecting that exception with type \RuntimeException will be thrown and if it is just \Exception - there is an error
Another question is how phpunit tell about it to you. In your case it shows message
1) PhpUnitBundle\Unit\ApplicationContext\Service\EntityManagerServiceTest::testUnsupportedAnnotationDriverException
Exception: Doctrine\ORM\Mapping\Driver\StaticPHPDriver is not a supported Annotation Driver.
It is not very obviouse.
You can make expectations more explicit if descibe them with setExpectedException:
/**
* Test fail if client requests a unsupported driver
*/
public function testUnsupportedAnnotationDriverException()
{
$entityManagerService = new EntityManagerService();
$this->setExpectedException(\RuntimeException::class);
// $this->setExpectedException('RuntimeException'); // for PHP < 5.5
$unsupportedDriver = 'Doctrine\ORM\Mapping\Driver\StaticPHPDriver';
$actualUnsupportedException = $entityManagerService->getAnnotationDriver(
$unsupportedDriver, __DIR__ . '/../../Helper'
);
}
or using proper annotation #expectedException:
/**
* Test fail if client requests a unsupported driver
*
* #expectedException \RuntimeException
*/
public function testUnsupportedAnnotationDriverException()
{
$entityManagerService = new EntityManagerService();
$unsupportedDriver = 'Doctrine\ORM\Mapping\Driver\StaticPHPDriver';
$actualUnsupportedException = $entityManagerService->getAnnotationDriver(
$unsupportedDriver, __DIR__ . '/../../Helper'
);
}
Now if you change throw \RuntimeException to throw \Exception in tested method, you can note that phpunit's message now look like this
Failed asserting that exception of type "Exception" matches expected exception "\RuntimeException". Message was: "Doctrine\ORM\Mapping\Driver\StaticPHPDriver is not a supported Annotation Driver."
So you see more explicit what is wrong
Related
I am working with a internal framework where every exception is catched by an error handler and returned in a proper JSON error response, suitable for a RESTFul API.
Then I have a suite of tests, which are API tests, that are mainly testing that the API returns the proper JSON responses with the expected error codes.
For every test, the global variables are modified (and then restored) to emulate a different HTTP request. I do it that way to avoid the overload of doing cURL tests (through Guzzle or similar), and cause under the CLI environment, the code does not know the server's url.
<?php
// ... example, part of a base ApiTestCase class:
// Override globals (should be backed up by PHPUnit)
$_SERVER['REQUEST_METHOD'] = $request->method;
$_SERVER['QUERY_STRING'] = http_build_query($request->parameters);
$_SERVER['PATH_INFO'] = $request->path;
$_SERVER['REQUEST_URI'] = $request->path . ($_SERVER['QUERY_STRING'] ? '?' : '') . $_SERVER['QUERY_STRING'];
$_SERVER['REQUEST_TIME'] = time();
$_SERVER['REQUEST_TIME_FLOAT'] = microtime(true);
$_SERVER['HTTP_COOKIE'] = '';
// Set headers, cookies and parameters
foreach ($request->headers as $k => $v) {
$_SERVER['HTTP_' . strtoupper(str_replace('-', '_', trim($k)))] = $v;
}
if ($_SERVER['HTTP_COOKIE']) {
$GLOBALS['_COOKIE'] = http_parse_cookie($_SERVER['HTTP_COOKIE']);
} else {
$GLOBALS['_COOKIE'] = [];
}
$GLOBALS['_REQUEST'] = $request->parameters;
$responseBody = $app->start();
$response->httpCode = http_response_code();
$response->body = $responseBody ? #json_decode($responseBody) : null;
$response->headers = headers_list();
(I know that changing globals this way is not nice, and the framework should not rely on globals directly, but I have still to deal with legacy code.)
Then here comes the problem: when I try to test JSON error responses: PHPUnit intercepts the thrown exception (before the handler I mentioned in the beginning), so the framework has no chance to convert it to JSON and return the proper response.
I tried to find something in the PHPUnit manual to disable the PHPUnit error handler with no luck.
What could I do in this case? Thanks
Just to be clear, it sounds like we're not actually talking about catching exceptions here; we're talking about using PHP's set_error_handler() to intercept a fatal error before it terminates the program. This will deal with both errors and uncaught exceptions.
One thing you will not be able to do is let those errors and exceptions fall through to your error handler function -- as you've already found out, phpUnit does its own error handling that you can't override (because it's kinda fundamental to how phpUnit works).
What you're going to have to do is tell phpUnit what kind of exception or error you're expecting; your test will then pass or fail according to whether the error occurs. You won't be running the error handler, but in truth, you shouldn't need to; you can test function that separately if you need to. For error conditions, you don't need to see that the error handler produces the right output every time, just that an error occurs that will trigger the handler.
For regular PHP exceptions, you can use phpUnit's #expectedException annotation above your test function, like so:
/**
* #expectedException YourExpectedExceptionClass
*/
function testThisWillThrowAnException() {
....
}
If the PHP code is expected to produce a PHP error (ie an error, not an exception), then you would use the same idea, but phpUnit provides a helper classname for the error: PHPUnit_Framework_Error. So your code would look like this:
/**
* #expectedException PHPUnit_Framework_Error
*/
function testThisWillProduceAPHPError() {
....
}
In either case, your test will pass if the expected error/exception occurs.
You can also test for specific exception messages and codes, in case the exception class itself isn't sufficient information for you to know whether the test has done what you want it to do. See the phpUnit manual page for annotations for more info.
The example above is also correct, mine only provide Exceptions as assertions and gives you knowladge of Exceptions Works.
/**
* #dataProvider fixturesProvider // its just example
*/
public function testDataIsWrong($fixtures)
{
try
{
//Some Code
$this->fail('Exception');
}
catch(Exception $ex)
{
$this->assertEquals($ex,'Exception');
}
}
This also provide in your code possibility ,that You can Test false or inncorect data and assert it is incorrect.
The only solution I implemented that solves my problem is to not delegate the exception handler the responsibility to build and send the API error responses, but catch exceptions in the top level of your application.
In the catch I have an exception-to-error-response converter that takes care of that (or re-throws the exception when convenient), so the errors that are not critical (like the ones producing HTTP 4xx responses) are not popping up in the PHPUnit tests anymore.
My PHPUnit tests are also now able to deal with PSR-7 HTTP Response objects, instead of capturing the output buffer.
I'm using Laravel 5.1 to create a console based application. During development I would like to display the exception trace when an error occurs. However, even if I use -v -vv or -vvv option in php artisan, I don't get an exception trace for my custom commands. I set APP_DEBUG=true in my .env, still no exception trace.
Output of php artisan some:unknowncommand is:
[InvalidArgumentException]
There are no commands defined in the "some" namespace.
Output of php artisan -v some:unknowncommand is:
[InvalidArgumentException]
There are no commands defined in the "some" namespace.
Exception trace:
() at /Users/dirkpostma/Dropbox/Domains/dpepp/vendor/symfony/console/Application.php:501
Symfony\Component\Console\Application->findNamespace() at /Users/dirkpostma/Dropbox/Domains/dpepp/vendor/symfony/console/Application.php:535
Symfony\Component\Console\Application->find() at /Users/dirkpostma/Dropbox/Domains/dpepp/vendor/symfony/console/Application.php:192
Symfony\Component\Console\Application->doRun() at /Users/dirkpostma/Dropbox/Domains/dpepp/vendor/symfony/console/Application.php:126
...
Now, I created a very simple console command called dp:test, with following handle function:
/**
* Execute the console command.
*
* #return mixed
*/
public function handle()
{
generate error here
}
Output of php artisan dp:test is:
[Symfony\Component\Debug\Exception\FatalErrorException]
syntax error, unexpected 'error' (T_STRING)
Output of php artisan -v dp:test is the same.
Output of php artisan -vvv dp:test is the same.
The log file DOES show the exception trace, so somehow it should be possible to display it in cli. I don't even see the filename and linenumer where the error occurs... How can I take care of this?
Thanks in advance!
EDIT:
Is digged a bit further. In case I use this in my Command:
public function handle()
{
throw new \Exception("test exception");
}
and I issue the command php artisan -v dp:test, the error trace IS printed. The trace is only not printed when the exception is thrown due to a PHP error. In Illuminate/Foundation/Bootstrap/HandleExceptions.php method bootstrap PHP errors are converted to Exceptions. When this occurs, an exception is thrown, but the -v is somehow ignored when printing. This is very inconvenient because it makes debugging CLI apps hard.
I think the solution can be found in vendor/symfony/console/Application.php, method renderException.
I'm going to dig further later, unless someone else can point the solution faster than me :-)
I found reason why -v is ignored:
in Illuminate/Foundation/Bootstrap/HandleExceptions.php, the renderForConsole method instantiates a ConsoleOutput object, with default settings, not taking into account the verbosity settings the user asked for:
protected function renderForConsole($e)
{
$this->getExceptionHandler()->renderForConsole(new ConsoleOutput, $e);
}
For this reason, whatever -v -vv or -vvv is set, the $output->getVerbosity() in vendor/symfony/console/Application.php is always lower than OutputInterface::VERBOSITY_VERBOSE, for that reason, the stack trace is not printed.
I probably start an issue on github for this, because I think it's much more convenient if errors are shown in CLI if user sets -v.
You can set the verbosity to whatever level you'd like by adding the following use statement:
use Symfony\Component\Console\Output\OutputInterface;
And then adding this to the top of your handle function:
$this->output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
See the documentation for symfony console here http://symfony.com/doc/current/console/verbosity.html
for more information.
If you look at Illuminate\Console\Command class you will see the definition of the function that writes the error string to the console:
/**
* Write a string as error output.
*
* #param string $string
* #return void
*/
public function error($string)
{
$this->output->writeln("<error>$string</error>");
}
Now that you see it only writes the error string, you can play just a little bit with it to achieve what you want. You can get the function call back trace and output as many lines and files backwards as you wish. Just create class that extends command, override the function and make all of your commands to inherit that command class:
/**
* Write a string as error output.
*
* #param string $string
* #return void
*/
public function error($string)
{
$this->output->writeln("<error>$string</error>");
$trace = debug_backtrace();
foreach ($trace as $t)
{
$this->output->writeln("Trace : " . $t['file'] . " on line " . $t['line'] . " function " . $t['function']);
}
}
I have narrowed my problem down to a single unit-test which involves a pretty clean dbal query. The query involves a nestedsets and the failure occurs when I go from 3 to 4 levels of hierarchical categories. Very little changes in terms of execution time.. And the functionality I'm work with has no problem at 10 level. However, in PhpUnit when I run the test with 4 or more levels -- I get a fatal error.
*To summarize, a query that may take a split second more than usual OR a tab bit more resources is getting me a php fatal error in PHPUnit *
And the Error Looks like this:
Fatal error: Call to undefined method Monolog\Formatter\LineFormatter::stopEvent() in /var/www/my-app/vendor/symfony/symfony/src/Symfony/Component/HttpKernel/Debug/Stopwatch.php on line 92
Someone here had a phpUnit fatal error problem on line 92 as well. Could it be related??
I've listed some quirky ways (below) to bypass my problem but I'd still like to know why such a basic test seems to be causing this problem.
Run PhpUnit with processIsolation set to "true".
Don't access the static::createClient(); in the setUpBeforeClass() method
I've had a respected member of #symfony (Stof) suggest that it looks like I'm improperly DI'ing #logger into a service.. but I've confirmed this doesn't seem to be the by taking the logger service out of any of my services.
Maybe this issue has something to do with a timeout? (The error mentions stopevent and stopwatch). Or maybe this error has something to do with accessing $client = static::createClient(); in setUpBeforeClass().. and then accessing is again in preceding tests.. Regardless.. it's confusing me and as far as I can tell, everything else in my Symfony2 (2.1.1) installation is working perfectly fine.
* LINE 92 of Stopwatch.php .. github source file here *
/**
* Stops an event.
* #param string $name The event name
* #return StopwatchEvent A StopwatchEvent instance
*/
public function stop($name)
{ return end($this->activeSections)->stopEvent($name);}
OK. So you grab the last item from the $this->activeSections array and they try to run the stopEvent() method on it. It would seem in this case that the LineFormatter class does not have that method available to it.
I am not familiar with Monolog, but in a brief glance at their latest GitHUb repo, I can see that neither the LineFormatter or NormalizerFormatter class which it inherits from have such a method.
It seems like Monolog is not playing nice with Symfony. And that without modifying Monolog and/or the Symfony Stopwatch class, you will not be able to use Stopwatch to profile your Monolog-related code.
I'm connecting to a third party service using SOAP. This service will return SoapFaults some times. Because this is expected, I want to test this, by mocking the SoapFaults. There are five standard SoapFaults it will return.
Here is the start of a real SoapFault:
object(SoapFault)#7 (11) {
["message":protected]=>
string(19) "{ 'INVALID_INPUT' }"
I am interested in this message. In my code I use $e->getMessage().
catch (SoapFault $e)
{
// Catch any Soap faults and convert to an error message
switch ($e->getMessage())
{
case "{ 'INVALID_INPUT' }":
$this->addError(self::INVALID_INPUT);
return FALSE;
but I cannot figure out how to mock the SoapClient response. The SoapClient object doesn't appear to take in any input to set the message, and the method getMessage() is final, so I can't mock it.
Any ideas would be appreciated. Thanks.
There should be no need to mock SoapFault. You can just use the real exception (usually exceptions are not mocked for testing as they are Value objects) and mock the SoapClient to throw your prepared exception.
By doing so you get around not being able to mock getMessage because you build the SoapFault in your test case like shown below.
As I've had troube figuring out what exactly your problem was i build a little test case to make sure it works out. As it was there already i guess the code explains it better than i could.
Test example:
<?php
class SoapFaultTest extends PHPUnit_Framework_TestCase {
public function testSoapFault() {
$soapFault = new SoapFault("test", "myMessage");
$soapClient = $this->getMockBuilder('SoapClient')
->setMethods(array('methodOnService'))
->disableOriginalConstructor()
->getMock();
$soapClient->expects($this->once())
->method('methodOnService')
->will($this->throwException($soapFault));
$x = new Consumer();
$this->assertSame(
"InvalidInput",
$x->doSoapStuff($soapClient)
);
}
}
class Consumer {
public function doSoapStuff(SoapClient $soapClient) {
try {
$soapClient->methodOnService();
} catch(Exception $e) {
if($e->getMessage() == "myMessage") {
return "InvalidInput";
}
}
}
}
Outputs:
phpunit SoapFaultTest.php
PHPUnit 3.6.3 by Sebastian Bergmann.
.
Time: 0 seconds, Memory: 3.75Mb
OK (1 test, 2 assertions)
The assertion is kept simple but you should be easily able to adapt this for your way of handling errors
Next to what edorian answered, there should be no need to test that a third-party component works as announced. Don't write tests for components you don't have written the code (or for which you can not write code, if they are broken, they're broken and you're lost).
Just test that your code works as expected. You should be able to mock your own code.
Example: Your code will always break if code or data of it is missing, e.g. files of your application get partially deleted under circumstances. Do you write tests for such scenarios? Naturally not.
You can however write yourself an integration suite that automatically tests if certain remote dependencies provide the expected interface and responses. But I would put that suite apart from your testsuites that cover your own code-base.
Additionally you can create your own assertion functions that can be used in each testcase, for example one for SoapClient Exception Message.
I want to thank hakre for his comment. This is exactly what I wanted.
The SoapFault extends Exception, which expects an input of ($message, $code, $previous).
But the SoapFault expects ($faultcode, $faultstring, $faultactor, $detail, $faultname, $headerfault).
This is what threw me.
The $faultstring maps to the exception message.
So to mock it, I did:
$mock_soap->expects($this->once())
->method('checkVat')
->will($this->throwException(new SoapFault('a', "Error Message")));
And in my real code
$e->getMessage() now returns 'Error Message'
When a user accesses /user/validate without the correct post parameters, my Zend application throws a zend exception. (I get the standard "An Error Occurred" message, framed within my layout). This is intentional.
I am now trying to test that behaviour with PHPUnit. Here's my test:
/**
* #expectedException Zend_Exception
*/
public function testEmptyUserValidationParametersCauseException() {
$this->dispatch('/user/validate');
}
When I run the test I get a message saying it failed, "Expected exception Zend_Exception". Any ideas?
I have other tests in the file which are working just fine...
Thanks!
The Zend_Controller_Plugin_ErrorHandler plugin handles exceptions for you and the default Error_Controller forces a 500 redirect which may mean the exception you are testing for no longer exists. Try the following unit test and see if it passes:
public function testUnknownUserRedirectsToErrorPage()
{
$this->dispatch('/user/validate');
$this->assertController('error');
$this->assertAction('error');
$this->assertResponseCode('500');
}
If this works then it shows that by the time you are rendering the error view the exception will no longer exist as it is contained in the code before the redirect.
Well, maybe it simply fails because no exception is thrown?
Did you try running the test without "#expectedException Zend_Exception"? Is there an exception at all?