WitteStier
WitteStier

Reputation: 411

PHPUnit Mock Exception

I have a class which handles errors, including exceptions. If an exception is caught, I will pass the exception as an argument to my exception/error handler.

try {
    someTrowingFnc();
} catch (\Exception $e) {
    this->error->exception($e);
}

Now I want to unit test this error handler and mock the exception.

I am finding it hard to mock the exception so that I can control the exception message, file and line.

$exceptionMock = $this->getMock('Exception', array(
    'getFile',
    'getLine',
    'getMessage',
    'getTrace'
)); // Tried all mock arguments like disable callOriginalConstructor

$exceptionMock->expects($this->any())
    ->method('getFile')
    ->willReturn('/file/name');

$exceptionMock->expects($this->any())
    ->method('getLine')
    ->willReturn('3069');

$exceptionMock->expects($this->any())
    ->method('getMessage')
    ->willReturn('Error test');

The results of the code below always returns NULL

$file   = $exception->getFile();
$line   = $exception->getLine();
$msg    = $exception->getMessage();

Is there a work-around to mock exceptions or am I just doing something wrong?

Upvotes: 6

Views: 6594

Answers (3)

Eugene Kaurov
Eugene Kaurov

Reputation: 2991

The throwException() in PHPUnit TestCase class can take any instance of Throwable as param.

Here is an example that should pass if you have try/catch in FileWriterToBeTested and will fail if you do not have try/catch:

    $this->reader = $this->getMockBuilder(Reader::class)->getMock();
    $this->reader->method('getFile')->will(static::throwException(new \Exception()));
    $file = new FileWriterToBeTested($this->reader);
    static::assertNull($file->getFile('someParamLikePath'));

tested class sample:

class FileWriterToBeTested
{

    /**
     * @var Reader
     */
    private $reader;

    public function __construct(Reader $reader): void
    {
        $this->reader = $reader;
    }

    /**
     * @return Reader
     */
    public function getFile(string $path): void
    {
        try {
            $this->reader->getFile($path);
        } catch (\Exception $e) {
            $this->error->exception($e);
        }        
    }

}

Upvotes: 0

DanielM
DanielM

Reputation: 6676

It's a bit of a hack, but try adding something like this to your TestCase:

/**
 * @param object $object        The object to update
 * @param string $attributeName The attribute to change
 * @param mixed  $value         The value to change it to
 */
protected function setObjectAttribute($object, $attributeName, $value)
{
    $reflection = new \ReflectionObject($object);
    $property = $reflection->getProperty($attributeName);
    $property->setAccessible(true);
    $property->setValue($object, $value);
}

Now you can change the values.

$exception = $this->getMock('Exception');
$this->setObjectAttribute($exception, 'file',    '/file/name');
$this->setObjectAttribute($exception, 'line',    3069);
$this->setObjectAttribute($exception, 'message', 'Error test');

Of course, you haven't really mocked the class, though this can still be useful if you have a more complex custom Exception. Also you won't be able to count how many times the method is called, but since you were using $this->any(), I assume that doesn't matter.

It's also useful when you're testing how an Exception is handled, for example to see if another method (such as a logger) was called with the the exception message as a parameter

Upvotes: 2

antoniovassell
antoniovassell

Reputation: 1032

The Exception class methods that return the error details such as getFile() etc are defined/declared as final methods. And this is one limitation of PHPUnit currently in mocking methods that are protected, private, and final.

Limitations
Please note that final, private and static methods cannot be stubbed or mocked. They are ignored by PHPUnit's test double functionality and retain their original behavior.

As seen here: https://phpunit.de/manual/current/en/test-doubles.html

Upvotes: 5

Related Questions