PHPUnit断言是否引发了异常?


337

有谁知道是否存在assert可以测试被测代码是否抛出异常的或类似东西?


2
对于那些答案:测试函数中的多断言怎么办?我只希望一个抛出异常?我是否需要将它们分开并将其置于独立的测试功能中?
Panwen Wang

Answers:


549
<?php
require_once 'PHPUnit/Framework.php';

class ExceptionTest extends PHPUnit_Framework_TestCase
{
    public function testException()
    {
        $this->expectException(InvalidArgumentException::class);
        // or for PHPUnit < 5.2
        // $this->setExpectedException(InvalidArgumentException::class);

        //...and then add your test code that generates the exception 
        exampleMethod($anInvalidArgument);
    }
}

ExpectException()PHPUnit文档

PHPUnit作者文章提供了有关测试异常最佳实践的详细说明。


7
如果使用名称空间,那么您需要输入完整的名称空间:$this->setExpectedException('\My\Name\Space\MyCustomException');
Alcalyn 2014年

15
您无法指定期望抛出的精确代码行,这是IMO错误。由于无法在同一测试中测试多个异常,因此对许多预期异常的测试确实很笨拙。我写了一个实际的断言来尝试解决这些问题。
mindplay.dk

18
仅供参考:自phpunit 5.2.0起,该 setExpectedException方法已被弃用,取而代之expectException。:)
hejdav '16

41
文档或此处未提及的内容,但是预期会引发异常的代码需要在after之后 调用expectException()。虽然对某些人来说可能很明显,但这对我来说却是一个陷阱
杰森·麦克雷里

7
从文档中看不出来,但是在引发异常的函数之后将不执行任何代码。因此,如果要在同一测试用例中测试多个异常,则不能。
劳伦特

122

在发布PHPUnit 9之前,您还可以使用docblock批注

class ExceptionTest extends PHPUnit_Framework_TestCase
{
    /**
     * @expectedException InvalidArgumentException
     */
    public function testException()
    {
        ...
    }
}

对于PHP 5.5+(尤其是带有命名空间的代码),我现在更喜欢使用 ::class


3
IMO,这是首选方法。
Mike Purcell 2012年

12
@LeviMorrison -恕我直言异常信息不应该被测试,类似日志消息。进行手动取证时,两者均被视为无关的有用信息。测试的关键点是异常类型。除此之外,其他任何东西都与实现紧密绑定。IncorrectPasswordException应该足够-消息等于是"Wrong password for bob@me.com"辅助消息。此外,您还希望花费尽可能少的时间来编写测试,然后您将开始了解简单测试的重要性。
David Harkness

5
@DavidHarkness我想有人会提出来的。同样,我同意测试消息总体上过于严格和严格。但是,在某些情况下(例如执行规范)可能需要(有目的地强调)严格性和紧密绑定。
维·莫里森

1
我不会看一个文档块来了解它的期望,但是我会看一下实际的测试代码(不管哪种测试)。这是所有其他测试的标准。我没有看到将Exceptions(哦上帝)作为该约定的例外的正当理由。
卡马菲瑟

3
“不测试消息”规则听起来是有效的,除非您测试的方法会在代码的多个部分中抛出相同的异常类型,唯一的区别是在消息中传递的错误ID。您的系统可能会根据Exception消息(不是Exception类型)向用户显示一条消息。在这种情况下,用户看到的消息并不重要,因此,您应该测试错误消息。
Vanja D.

34

如果你在PHP 5.5以上运行时,你可以用::class分辨率来获得与类的名称expectException/setExpectedException。这提供了几个好处:

  • 该名称将带有其名称空间(如果有)完全限定。
  • 它解析为a,string因此可以与任何版本的PHPUnit一起使用。
  • 您将在IDE中完成代码补全。
  • 如果您键入错误的类名,PHP编译器将发出错误。

例:

namespace \My\Cool\Package;

class AuthTest extends \PHPUnit_Framework_TestCase
{
    public function testLoginFailsForWrongPassword()
    {
        $this->expectException(WrongPasswordException::class);
        Auth::login('Bob', 'wrong');
    }
}

PHP编译

WrongPasswordException::class

进入

"\My\Cool\Package\WrongPasswordException"

没有PHPUnit是更明智的选择。

注意引入PHPUnit 5.2 expectException替代setExpectedException


32

下面的代码将测试异常消息和异常代码。

重要说明:如果也未引发预期的异常,它将失败。

try{
    $test->methodWhichWillThrowException();//if this method not throw exception it must be fail too.
    $this->fail("Expected exception 1162011 not thrown");
}catch(MySpecificException $e){ //Not catching a generic Exception or the fail function is also catched
    $this->assertEquals(1162011, $e->getCode());
    $this->assertEquals("Exception Message", $e->getMessage());
}

6
$this->fail()我不认为这种方式不会被使用,至少目前不会(PHPUnit 3.6.11);它本身就是一个例外。以您的示例为例,如果$this->fail("Expected exception not thrown")调用,则会catch触发该块,并且该块$e->getMessage()“未引发预期异常”
2014年

1
@肯,你可能是对的。要将呼叫fail可能属于 catch块,而不是尝试内。
Frank Farmer 2014年

1
我必须投反对票,因为对的调用fail不应在该try区域中。它本身会触发catch生成错误结果的块。
2015年

6
我认为这在某些情况下无法正常运行的原因是它捕获了的所有异常catch(Exception $e)。当我尝试捕获特定异常时,此方法对我来说效果很好:try { throw new MySpecificException; $this->fail('MySpecificException not thrown'); } catch(MySpecificException $e){}
spyle 2015年

23

您可以使用assertException扩展来在一个测试执行期间声明一个以上的异常。

将方法插入TestCase并使用:

public function testSomething()
{
    $test = function() {
        // some code that has to throw an exception
    };
    $this->assertException( $test, 'InvalidArgumentException', 100, 'expected message' );
}

我还为喜欢精美代码的爱好者们赋予了一个特质


您正在使用哪个PHPUnit?我正在使用PHPUnit 4.7.5,但assertException未定义。我也无法在PHPUnit手册中找到它。
物理

2
asertException方法不是原始PHPUnit的一部分。您必须继承PHPUnit_Framework_TestCase该类并手动添加上面发布的链接的方法。然后,您的测试用例将继承此继承的类。
hejdav


13

PHPUnit expectException方法非常不便,因为每个测试方法只能测试一个异常。

我使这个辅助函数断言某些函数引发异常:

/**
 * Asserts that the given callback throws the given exception.
 *
 * @param string $expectClass The name of the expected exception class
 * @param callable $callback A callback which should throw the exception
 */
protected function assertException(string $expectClass, callable $callback)
{
    try {
        $callback();
    } catch (\Throwable $exception) {
        $this->assertInstanceOf($expectClass, $exception, 'An invalid exception was thrown');
        return;
    }

    $this->fail('No exception was thrown');
}

将其添加到您的测试班级,并以这种方式调用:

public function testSomething() {
    $this->assertException(\PDOException::class, function() {
        new \PDO('bad:param');
    });
    $this->assertException(\PDOException::class, function() {
        new \PDO('foo:bar');
    });
}

绝对是所有答案中最好的解决方案!将其放入特征并打包!
domdambrogia

11

综合解决方案

PHPUnit当前用于异常测试的“ 最佳实践 ”似乎..不足为docs)。

由于我想要的不仅仅是当前的expectException实现,因此我在测试用例中使用了一个特质。大约只有50行代码

  • 支持每个测试多个异常
  • 支持引发异常后调用的断言
  • 稳健而清晰的用法示例
  • 标准assert语法
  • 支持断言,不仅限于消息,代码和类
  • 支持反断言, assertNotThrows
  • 支持PHP 7 Throwable错误

图书馆

我将AssertThrows特征发布给Github和packagist,以便可以与作曲家一起安装。

简单的例子

只是为了说明语法背后的精神:

<?php

// Using simple callback
$this->assertThrows(MyException::class, [$obj, 'doSomethingBad']);

// Using anonymous function
$this->assertThrows(MyException::class, function() use ($obj) {
    $obj->doSomethingBad();
});

挺整洁的?


完整用法示例

请参阅下面的更全面的用法示例:

<?php

declare(strict_types=1);

use Jchook\AssertThrows\AssertThrows;
use PHPUnit\Framework\TestCase;

// These are just for illustration
use MyNamespace\MyException;
use MyNamespace\MyObject;

final class MyTest extends TestCase
{
    use AssertThrows; // <--- adds the assertThrows method

    public function testMyObject()
    {
        $obj = new MyObject();

        // Test a basic exception is thrown
        $this->assertThrows(MyException::class, function() use ($obj) {
            $obj->doSomethingBad();
        });

        // Test custom aspects of a custom extension class
        $this->assertThrows(MyException::class, 
            function() use ($obj) {
                $obj->doSomethingBad();
            },
            function($exception) {
                $this->assertEquals('Expected value', $exception->getCustomThing());
                $this->assertEquals(123, $exception->getCode());
            }
        );

        // Test that a specific exception is NOT thrown
        $this->assertNotThrows(MyException::class, function() use ($obj) {
            $obj->doSomethingGood();
        });
    }
}

?>

4
有点讽刺的是,您用于单元测试的软件包在回购中不包含单元测试。
domdambrogia

2
@domdambrogia感谢@ jean-beguin,它现在可以进行单元测试。
jchook

8
public function testException() {
    try {
        $this->methodThatThrowsException();
        $this->fail("Expected Exception has not been raised.");
    } catch (Exception $ex) {
        $this->assertEquals($ex->getMessage(), "Exception message");
    }

}

在您的示例中,签名为assertEquals()is assertEquals(mixed $expected, mixed $actual...),所以为反向,因此应该为$this->assertEquals("Exception message", $ex->getMessage());
Roger Campanera

7

这是您可以执行的所有异常断言。请注意,它们都是可选的

class ExceptionTest extends PHPUnit_Framework_TestCase
{
    public function testException()
    {
        // make your exception assertions
        $this->expectException(InvalidArgumentException::class);
        // if you use namespaces:
        // $this->expectException('\Namespace\MyExceptio‌​n');
        $this->expectExceptionMessage('message');
        $this->expectExceptionMessageRegExp('/essage$/');
        $this->expectExceptionCode(123);
        // code that throws an exception
        throw new InvalidArgumentException('message', 123);
   }

   public function testAnotherException()
   {
        // repeat as needed
        $this->expectException(Exception::class);
        throw new Exception('Oh no!');
    }
}

文档可以在这里找到。


这是不正确的,因为PHP在第一个引发的异常上停止。PHPUnit检查抛出的异常是否具有正确的类型,并说“测试正常”,它甚至不知道第二个异常。
Finesse's October

3
/**
 * @expectedException Exception
 * @expectedExceptionMessage Amount has to be bigger then 0!
 */
public function testDepositNegative()
{
    $this->account->deposit(-7);
}

注意"/**",请注意双“ *”。只写“ **”(星号)将使您的代码失败。还要确保您使用的是最新版本的phpUnit。在某些早期版本的phpunit中,@expectedException不支持异常。我有4.0,但它对我不起作用,我必须更新到5.5 https://coderwall.com/p/mklvdw/install-phpunit-with-composer才能使用composer进行更新。


0

对于PHPUnit 5.7.27和PHP 5.6并在一个测试中测试多个异常,强制进行异常测试很重要。如果没有异常发生,仅使用异常处理来声明Exception实例将跳过测试情况。

public function testSomeFunction() {

    $e=null;
    $targetClassObj= new TargetClass();
    try {
        $targetClassObj->doSomething();
    } catch ( \Exception $e ) {
    }
    $this->assertInstanceOf(\Exception::class,$e);
    $this->assertEquals('Some message',$e->getMessage());

    $e=null;
    try {
        $targetClassObj->doSomethingElse();
    } catch ( Exception $e ) {
    }
    $this->assertInstanceOf(\Exception::class,$e);
    $this->assertEquals('Another message',$e->getMessage());

}

0
function yourfunction($a,$z){
   if($a<$z){ throw new <YOUR_EXCEPTION>; }
}

这是测试

class FunctionTest extends \PHPUnit_Framework_TestCase{

   public function testException(){

      $this->setExpectedException(<YOUR_EXCEPTION>::class);
      yourfunction(1,2);//add vars that cause the exception 

   }

}

0

PhpUnit是一个了不起的库,但是这一点有点令人沮丧。这就是为什么我们可以使用turbotesting-php开源库的原因,该库具有非常方便的断言方法来帮助我们测试异常。在这里找到:

https://github.com/edertone/TurboTesting/blob/master/TurboTesting-Php/src/main/php/utils/AssertUtils.php

要使用它,我们只需执行以下操作:

AssertUtils::throwsException(function(){

    // Some code that must throw an exception here

}, '/expected error message/');

如果我们在匿名函数中键入的代码未引发异常,则将引发异常。

如果我们在匿名函数中键入的代码引发异常,但其消息与预期的正则表达式不匹配,则也会引发异常。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.