Phpunit - mocking SoapFault->getMessage() - php

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'

Related

PHPUnit and Domain Exception Testing

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

how to make PHPUnit stop intercepting exceptions?

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.

When running unit tests with laravel, how do you test your App::error() implementations?

I'm currently working on an open source personal project that provides a nice backend api for game developers. I'm in the early stages of development, but I plan to write tests as I go along, which is where I've hit a snag.
Through out the system when an error occurs such as incorrect api credentials or missing credentials, I throw a custom exception which stores a bit of extra data so that I can catch it and give a JSON encoded response.
The tests work fine for those thrown in my BaseController, but I also capture a few Laravel Exceptions so I can respond with my own, or at least, output JSON like below:
app/start/global.php
App::error(function(Exception $exception, $code) {
Log::error($exception);
});
App::missing(function(Exception $exception) {
return BaseController::error(
Config::get('response.method.code'),
Config::get('response.method.http'),
'Method not found'
);
});
App::error(function(Viper\Exception $exception) {
return BaseController::error(
$exception->getCode(),
$exception->getStatusCode(),
$exception->getMessage()
);
});
I'm using the try { } catch() { } approach as I need to check an extra value that isn't in the normal Exceptions.
public function testNoMethodGET() {
$config = Config::get('response.method');
try {
$this->call('GET', '/');
} catch(\Viper\Exception $e) {
$this->assertEquals($e->getCode(), $config['code']);
$this->assertEquals($e->getStatusCode(), $config['http']);
}
$this->fail('Exception not thrown');
}
This is all good and well, but I want to check a few things on the actual response, like for example, whether or not the json is valid, whether or not the response structure matches and whether or not the response values are correct.
If I set the return value of $this->call() to a variable, I'd be unable to access that variable within the catch block, so the question is this, how can I test the return value of $this->call() once the Exception has been caught?
According to Taylor Otwell:
"this can be solved by de-coupling your
test. You really want to test the handler and that the exception is
thrown totally separately anyways [sic] to isolate your tests. For
instance:
App::error(function(ErrorType $e)
{
App::make('ErrorTypeHandler')->handle($e);
});
Now you can write test cases for ErrorTypeHandler class separately
from the rest of your application. Then check that proper exceptions
are thrown by your app with #expectedException."
see How do you test your App::error implementations?
In your case, you already have isolated your error handler in BaseController::error(), so you can test the responses directly in separate unit tests, without the use of $this->call(). Instead, just call $response = BaseController::error() with the desired parameters and then inspect the response and apply relevant assertions.

Magento/PHPUnit - force an exception

I'm using phpunit and the ecomdev module to unit test Magento. For a particular class the last code that needs to be tested is an exception on attempting to save a model in a try/catch. The only way I can see to get to the exception is alter the database temporarily. So I changed the table name which works, but then I have to name it back. If the test itself fails I'm in an inconsistent state.
I'd really like to get that code tested so the coverage is 100%. Otherwise I'll be wondering why it's 98% until I look at the code and remember the exception isn't tested.
I was trying to close the connection but I need it to log the exception. So I'm wondering if maybe there is something I can temporarily do to the model or resource model to cause the save to raise an exception. Something that would be reset on the next load.
One note - I can't see anyway that manipulating data will cause the exception. Again the only scenario I see causing an exception in production is if the database connection goes away.
Any ideas?
Edit:
Sample Code:
public function logStuff($stuff){
try{
Mage::getModel('my/stuff')
->setData('stuff', $stuff)
->save();
}catch(Exception $e){
Mage::helper('log/error')->logError(__METHOD__, "Could not save stuff: ". $e->getMessage());
}
}
To make test the exception catch, you need to replace your model instance with the mocked one.
It is very easy to achieve with EcomDev_PHPUnit extension:
$mockedStuff = $this->getModelMock('your/stuff', array('save'));
$mockedStuff->expects($this->once()) // How many times it is invoked
->method('save') // Wich method to mock
->will($this->returnCallback(function () {
throw new Exception('Some error text');
})); // Your exception
$this->replaceByMock('model', 'your/stuff', $mockedStuff);
Also you can evaluate your logging stuff by using mock as well.
$loggerMock = $this->getHelperMock('log/error', array('logError'));
$loggerMock->expects($this->once())
->method('logError')
->with('className::methodName', 'Could not save stuff: Some error text'); // Check that correct arguments passed
$this->replaceByMock('helper', 'log/error', $loggerMock);
Have fun with unit tests :)

How to indicate that a PHPUnit test is expected to fail?

Is it possible to mark a test as "expected to fail" with PHPUnit? This would be useful when performing TDD, and you want to distinguish between genuinely failed tests, and tests that happen to fail because the associated code hasn't been written yet.
I think in these cases, it's fairly standard to simply mark the test as skipped. Your tests will still run and the suite will pass, but the test runner will alert you of the skipped tests.
http://phpunit.de/manual/current/en/incomplete-and-skipped-tests.html
The 'correct' method of handling this is to use $this->markTestIncomplete(). This will mark the test as incomplete. It will come back as passed, but it will display the message provided. See http://www.phpunit.de/manual/3.0/en/incomplete-and-skipped-tests.html for more information.
I really think it's a bad practice, however you can trick PHPUnit this way:
/**
* This test will succeed !!!
* #expectedException PHPUnit_Framework_ExpectationFailedException
*/
public function testSucceed()
{
$this->assertTrue(false);
}
More cleanly:
public function testFailingTest() {
try {
$this->assertTrue(false);
} catch (PHPUnit_Framework_ExpectationFailedException $ex) {
// As expected the assertion failed, silently return
return;
}
// The assertion did not fail, make the test fail
$this->fail('This test did not fail as expected');
}
The comment by sixty-nine above is nearly perfect for what I was searching for.
The fail() method is useful for when you set a test for an expected exception and if it did not trigger the exception you want the test to fail.
$this->object->triggerException();
$this->fail('The above statement was expected to trigger and exception.');
Of course the triggerException is replaced by something in your object.
If you want to have a test fail but know that its failure was expected, you can add a message to the assertion that will output in the results:
public function testExpectedToFail()
{
$this->assertTrue(FALSE, 'I knew this would happen!');
}
In the results:
There was 1 failure:
1) testExpectedToFail(ClassTest)
I knew this would happen!
In PHPUnit 8.2.5 you can simply expect the thrown assertion exception:
$this->expectException('PHPUnit\Framework\ExpectationFailedException');
$this->assertTrue(false);

Categories