Exception PHPUnit模拟异常

Exception PHPUnit模拟异常,exception,error-handling,phpunit,Exception,Error Handling,Phpunit,我有一个类处理错误,包括异常。如果捕获到异常,我将把异常作为参数传递给我的异常/错误处理程序 try { someTrowingFnc(); } catch (\Exception $e) { this->error->exception($e); } 现在我想对这个错误处理程序进行单元测试并模拟异常 我发现很难模拟异常,以便控制异常消息、文件和行 $exceptionMock = $this->getMock('Exception', array(

我有一个类处理错误,包括异常。如果捕获到异常,我将把异常作为参数传递给我的异常/错误处理程序

try {
    someTrowingFnc();
} catch (\Exception $e) {
    this->error->exception($e);
}
现在我想对这个错误处理程序进行单元测试并模拟异常

我发现很难模拟异常,以便控制异常消息、文件和行

$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');
下面代码的结果总是返回NULL

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

是否有办法模拟异常,或者我只是做错了什么?

返回错误详细信息的异常类方法(如
getFile()
等)被定义/声明为
final
方法。这是PHPUnit目前在模拟受保护、私有和最终的方法时的一个限制

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.

如图所示:

这有点像黑客,但请尝试在测试用例中添加类似的内容:

/**
*@param object$object要更新的对象
*@param string$attributeName指定要更改的属性
*@param mixed$value要将其更改为的值
*/
受保护的函数setObjectAttribute($object、$attributeName、$value)
{
$reflection=new\ReflectionObject($object);
$property=$reflection->getProperty($attributeName);
$property->setAccessible(true);
$property->setValue($object,$value);
}
现在您可以更改这些值

$exception=$this->getMock('exception');
$this->setObjectAttribute($exception,'file','/file/name');
$this->setObjectAttribute($exception,'line',3069);
$this->setObjectAttribute($exception,'message','Error test');
当然,您还没有真正模拟该类,不过如果您有更复杂的自定义异常,这仍然很有用。此外,您无法计算该方法被调用的次数,但由于您使用的是
$this->any()
,因此我认为这并不重要

当您测试如何处理异常时,它也很有用,例如查看是否调用了另一个方法(如记录器),并将异常消息作为参数

PHPUnit TestCase类中的throwException()可以将Throwable的任何实例作为参数

下面是一个示例,如果您在FileWriterToBeTested中有try/catch,则该示例应通过,如果您没有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'));
测试类样本:

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

}

您的对象是否正在引发异常?否则,您可以生成该模拟,但如果没有任何东西抛出它,或者您没有生成该条件,则该模拟不会发生。有可能显示所有的代码,或者至少是类的代码吗?我从2011年起就没有使用过phpunit,所以请记住,但在我的脑海中,我记得您有一个装饰程序来捕获预期的异常,但是您的情况似乎有点不同,您正在生成一个模拟,但是(这是我的假设),您没有生成引发异常本身的条件,因此您的断言将失败。我认为您可以创建一个虚拟类来实现您的异常类。在该函数中,您将在调用时显式抛出错误,这样您将知道确切的文件、行号等。使用测试用例调用该函数并捕获抛出的显式异常,断言文件、行号等是正确的。