6

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?

1
  • 1
    Is your object throwing the exception ? Otherwise, you can generate that mock but if nothing throws it, or you don't generate that condition, that mock won't happen. Would it be possible to show all the code, or at least the code of the class?, I haven't worked with phpunit since 2011, so keep that in mind, but top of my head, I recall that you have a decorator to capture an expected exception, but your case seems a tad different, you are generating a mock, but (this is my assumption), you are not generating the condition to throw the exception itself, so your asserts will fail. Commented Sep 13, 2014 at 3:10

3 Answers 3

5

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

Sign up to request clarification or add additional context in comments.

1 Comment

Am thinking you could create a dummy class that implements your exception class. In that function you would explicitly throw an error when called, that way you would know the exact file, line number, etc. Call the function using a test case and catch the explicit exception thrown, assert that the file, line number, etc are correct.
2

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

Comments

0

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);
        }        
    }

}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.