【问题标题】:PHPUnit Mock ExceptionPHPUnit 模拟异常
【发布时间】:2014-11-07 04:11:14
【问题描述】:

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

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

是否有解决方法来模拟异常或者我只是做错了什么?

【问题讨论】:

  • 您的对象是否抛出异常?否则,您可以生成该模拟,但如果没有抛出它,或者您没有生成该条件,则该模拟将不会发生。是否有可能显示所有代码,或者至少显示类的代码?,我从 2011 年起就没有使用过 phpunit,所以请记住这一点,但在我的脑海中,我记得你有一个装饰器捕获一个预期的异常,但你的情况似乎有点不同,你正在生成一个模拟,但是(这是我的假设),你没有生成抛出异常本身的条件,所以你的断言将失败。

标签: exception error-handling phpunit


【解决方案1】:

返回错误详细信息的 Exception 类方法(例如 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.

如此处所示:https://phpunit.de/manual/current/en/test-doubles.html

【讨论】:

  • 我认为您可以创建一个虚拟类来实现您的异常类。在该函数中,您将在调用时显式抛出错误,这样您就可以知道确切的文件、行号等。使用测试用例调用该函数并捕获抛出的显式异常,断言文件、行号等是正确。
【解决方案2】:

这有点小技巧,但请尝试在您的 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);
}

现在您可以更改这些值。

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

当然,您还没有真正模拟过该类,尽管如果您有更复杂的自定义异常,这仍然很有用。您也无法计算该方法被调用了多少次,但由于您使用的是$this->any(),我认为这并不重要。

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-02-19
      • 1970-01-01
      • 2013-03-09
      • 2018-07-22
      • 2017-01-20
      • 2014-07-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多