PHPUnit khẳng định rằng một ngoại lệ đã được ném?


337

Có ai biết liệu có một asserthoặc một cái gì đó tương tự có thể kiểm tra xem một ngoại lệ đã được ném trong mã đang được thử nghiệm không?


2
Đối với những câu trả lời: những gì về đa xác nhận trong một chức năng kiểm tra, và tôi chỉ mong có một ngoại lệ ném? Tôi CÓ PHẢI tách chúng ra và đặt một trong một chức năng kiểm tra độc lập không?
Panwen Wang

Câu trả lời:


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

tài liệu mong đợiException () PHPUnit

Bài viết của tác giả PHPUnit cung cấp giải thích chi tiết về kiểm tra các ngoại lệ thực tiễn tốt nhất.


7
Nếu bạn sử dụng không gian tên, bạn cần phải nhập không gian tên đầy đủ:$this->setExpectedException('\My\Name\Space\MyCustomException');
Alcalyn

15
Thực tế là bạn không thể chỉ định dòng mã chính xác dự kiến ​​sẽ ném, là một lỗi IMO. Và việc không thể kiểm tra nhiều hơn một ngoại lệ trong cùng một bài kiểm tra, khiến cho việc kiểm tra nhiều trường hợp ngoại lệ được mong đợi trở thành một vấn đề thực sự khó hiểu. Tôi đã viết một khẳng định thực tế để cố gắng giải quyết những vấn đề đó.
mindplay.dk

18
FYI: kể từ phương thức phpunit 5.2.0 setExpectedException không được dùng nữa, thay thế bằng phương thức này expectException. :)
hejdav

41
Những gì không được đề cập trong các tài liệu hoặc ở đây, nhưng mã dự kiến ​​sẽ đưa ra một ngoại lệ cần phải được gọi sau expectException() . Trong khi nó có thể đã hiển nhiên đối với một số người, đó là một Gotcha cho tôi.
Jason McCreary

7
Không rõ ràng từ tài liệu, nhưng không có mã nào sau khi hàm của bạn ném ngoại lệ sẽ được thực thi. Vì vậy, nếu bạn muốn kiểm tra nhiều ngoại lệ trong cùng một trường hợp kiểm tra, bạn không thể.
nguyệt quế

122

Bạn cũng có thể sử dụng chú thích docblock cho đến khi PHPUnit 9 được phát hành:

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

Đối với PHP 5.5+ (đặc biệt là với mã được đặt tên), bây giờ tôi thích sử dụng ::class


3
IMO, đây là phương pháp ưa thích.
Mike Purcell

12
@LeviMorrison - IMHO không nên kiểm tra thông báo ngoại lệ , tương tự như thông điệp tường trình . Cả hai đều được coi là không liên quan, thông tin hữu ích khi thực hiện thủ pháp y. Điểm quan trọng để kiểm tra là loại ngoại lệ. Bất cứ điều gì ngoài đó là ràng buộc quá chặt chẽ để thực hiện. IncorrectPasswordExceptionnên là đủ - rằng thông điệp bằng "Wrong password for bob@me.com"là phụ trợ. Thêm vào đó bạn muốn dành ít thời gian viết bài kiểm tra nhất có thể, và bạn bắt đầu thấy các bài kiểm tra đơn giản quan trọng như thế nào.
David Harkness

5
@DavidHarkness Tôi hình dung ai đó sẽ đưa nó lên. Tương tự như vậy, tôi đồng ý rằng các thông báo kiểm tra nói chung là quá nghiêm ngặt và chặt chẽ. Tuy nhiên, chính sự nghiêm ngặt và ràng buộc chặt chẽ có thể (nhấn mạnh có chủ đích) là những gì được mong muốn trong một số tình huống, chẳng hạn như việc thực thi một thông số kỹ thuật.
Levi Morrison

1
Tôi sẽ không xem trong khối tài liệu để hiểu những gì nó mong đợi, nhưng tôi sẽ xem mã kiểm tra thực tế (bất kể loại thử nghiệm nào). Đó là tiêu chuẩn cho tất cả các bài kiểm tra khác; Tôi không thấy các lý do hợp lệ để Ngoại lệ trở thành (trời ơi) là một ngoại lệ đối với quy ước này.
Kamafeather

3
Quy tắc "không kiểm tra thông báo" nghe có vẻ hợp lệ, trừ khi bạn kiểm tra một phương thức ném cùng một loại ngoại lệ trong nhiều phần mã, với sự khác biệt duy nhất là id lỗi, được truyền trong thông báo. Hệ thống của bạn có thể hiển thị một thông báo cho người dùng dựa trên thông báo Ngoại lệ (không phải loại Ngoại lệ). Trong trường hợp đó, người dùng nhìn thấy thông báo nào không quan trọng, do đó, bạn nên kiểm tra thông báo lỗi.
Vanja D.

34

Nếu bạn đang chạy trên PHP 5.5+, bạn có thể sử dụng ::classđộ phân giải để lấy tên của lớp có expectException/setExpectedException . Điều này cung cấp một số lợi ích:

  • Tên sẽ đủ điều kiện với không gian tên của nó (nếu có).
  • Nó giải quyết vấn đề stringvì vậy nó sẽ hoạt động với bất kỳ phiên bản PHPUnit nào.
  • Bạn nhận được hoàn thành mã trong IDE của bạn.
  • Trình biên dịch PHP sẽ phát ra lỗi nếu bạn gõ nhầm tên lớp.

Thí dụ:

namespace \My\Cool\Package;

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

Biên dịch PHP

WrongPasswordException::class

vào

"\My\Cool\Package\WrongPasswordException"

không có PHPUnit là người khôn ngoan hơn.

Lưu ý : PHPUnit 5.2 được giới thiệu expectException thay thế cho setExpectedException.


32

Mã dưới đây sẽ kiểm tra thông báo ngoại lệ và mã ngoại lệ.

Quan trọng: Nó sẽ thất bại nếu ngoại lệ dự kiến ​​không được ném quá.

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()Tôi không có ý định sử dụng theo cách này mà tôi không nghĩ, ít nhất là hiện tại (PHPUnit 3.6.11); nó hoạt động như một ngoại lệ Sử dụng ví dụ của bạn, nếu $this->fail("Expected exception not thrown")được gọi, thì catchkhối được kích hoạt và $e->getMessage()"Ngoại lệ dự kiến ​​không được ném" .
ken

1
@ken có lẽ bạn đúng. Các cuộc gọi đến failcó lẽ thuộc về sau khối catch, không bên trong thử.
Nông dân Frank

1
Tôi phải downvote vì cuộc gọi failkhông nên trong trykhối. Chính nó kích hoạt catchkhối tạo ra kết quả sai.
Hai mươi

6
Tôi tin rằng lý do điều này không hoạt động tốt là một số tình huống là nó bắt được tất cả các ngoại lệ catch(Exception $e). Phương pháp này hoạt động khá tốt đối với tôi khi tôi cố gắng bắt các Ngoại lệ cụ thể:try { throw new MySpecificException; $this->fail('MySpecificException not thrown'); } catch(MySpecificException $e){}
spyle

23

Bạn có thể sử dụng tiện ích mở rộng assertException để xác nhận nhiều hơn một ngoại lệ trong một lần thực hiện kiểm tra.

Chèn phương thức vào TestCase của bạn và sử dụng:

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

Tôi cũng đã tạo ra một đặc điểm cho những người yêu thích mã đẹp ..


Bạn đang sử dụng PHPUnit nào? Tôi đang sử dụng PHPUnit 4.7.5 và assertExceptionkhông được xác định. Tôi cũng không thể tìm thấy nó trong hướng dẫn PHPUnit.
vật lý

2
Các asertExceptionphương pháp không phải là một phần của PHPUnit gốc. Bạn phải kế thừa PHPUnit_Framework_TestCaselớp và thêm phương thức được liên kết trong bài viết ở trên . Các trường hợp thử nghiệm của bạn sau đó sẽ kế thừa lớp kế thừa này.
hejdav

18

Một cách khác có thể là như sau:

$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Expected Exception Message');

Hãy đảm bảo rằng lớp thử nghiệm của bạn mở rộng \PHPUnit_Framework_TestCase.


Để chắc chắn nhiều đường nhất trong cú pháp này
AndrewMcLagan

13

expectExceptionPhương thức PHPUnit rất bất tiện vì nó chỉ cho phép kiểm tra một ngoại lệ cho mỗi phương thức kiểm tra.

Tôi đã thực hiện chức năng trợ giúp này để khẳng định rằng một số chức năng đưa ra một ngoại lệ:

/**
 * 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');
}

Thêm nó vào lớp thử nghiệm của bạn và gọi theo cách này:

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

Chắc chắn là giải pháp tốt nhất trong số tất cả các câu trả lời! Ném nó vào một đặc điểm và gói nó!
domdambrogia

11

Giải pháp toàn diện

"Các thực tiễn tốt nhất " hiện tại của PHPUnit để kiểm tra ngoại lệ dường như .. mờ nhạt ( tài liệu ).

Vì tôi muốn nhiều hơn việc expectExceptiontriển khai hiện tại , tôi đã tạo ra một đặc điểm để sử dụng cho các trường hợp thử nghiệm của mình. Chỉ có ~ 50 dòng mã .

  • Hỗ trợ nhiều ngoại lệ cho mỗi bài kiểm tra
  • Hỗ trợ các xác nhận được gọi sau khi ném ngoại lệ
  • Ví dụ sử dụng mạnh mẽ và rõ ràng
  • assertCú pháp chuẩn
  • Hỗ trợ các xác nhận không chỉ cho tin nhắn, mã và lớp
  • Hỗ trợ xác nhận nghịch đảo, assertNotThrows
  • Hỗ trợ Throwablelỗi PHP 7

Thư viện

Tôi đã xuất bản AssertThrowsđặc điểm này cho Github và packagist để nó có thể được cài đặt với trình soạn thảo.

Ví dụ đơn giản

Chỉ để minh họa tinh thần đằng sau cú pháp:

<?php

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

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

Khá gọn gàng?


Ví dụ sử dụng đầy đủ

Vui lòng xem bên dưới để biết ví dụ sử dụng toàn diện hơn:

<?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
Một chút mỉa mai rằng gói của bạn để kiểm tra đơn vị không bao gồm kiểm tra đơn vị trong repo.
domdambrogia

2
@domdambrogia nhờ @ jean-beguin giờ đây nó đã có bài kiểm tra đơn vị.
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");
    }

}

Chữ ký của assertEquals()assertEquals(mixed $expected, mixed $actual...), đảo ngược như trong ví dụ của bạn, vì vậy nó phải được$this->assertEquals("Exception message", $ex->getMessage());
Roger Campanera

7

Đây là tất cả các xác nhận ngoại lệ bạn có thể làm. Lưu ý rằng tất cả chúng là tùy chọn .

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

Tài liệu có thể được tìm thấy ở đây .


Nó không chính xác vì PHP dừng ở ngoại lệ ném đầu tiên. PHPUnit kiểm tra xem ngoại lệ bị ném có đúng loại không và nói «kiểm tra là ổn», thậm chí còn không biết về ngoại lệ thứ hai.
Phạt tiền

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

Hãy thật cẩn thận "/**", chú ý gấp đôi "*". Chỉ viết "**" (dấu hoa thị) sẽ không thành công mã của bạn. Cũng đảm bảo rằng bạn sử dụng phiên bản cuối cùng của phpUnit. Trong một số phiên bản trước đó của phpunit @azedException Exception không được hỗ trợ. Tôi đã có 4.0 và nó không hoạt động với tôi, tôi phải cập nhật lên 5.5 https://coderwall.com/p/mklvdw/install-phastait-with-composer để cập nhật với nhà soạn nhạc.


0

Đối với PHPUnit 5.7.27 và PHP 5.6 và để kiểm tra nhiều ngoại lệ trong một thử nghiệm, điều quan trọng là bắt buộc thử nghiệm ngoại lệ. Sử dụng xử lý ngoại lệ một mình để khẳng định trường hợp Ngoại lệ sẽ bỏ qua việc kiểm tra tình huống nếu không có ngoại lệ xảy ra.

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

đây là bài kiểm tra

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 là một thư viện tuyệt vời, nhưng điểm cụ thể này là một chút bực bội. Đây là lý do tại sao chúng ta có thể sử dụng thư viện mã nguồn mở turbotesting-php có phương thức xác nhận rất thuận tiện để giúp chúng tôi kiểm tra các ngoại lệ. Nó được tìm thấy ở đây:

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

Và để sử dụng nó, chúng ta chỉ cần làm như sau:

AssertUtils::throwsException(function(){

    // Some code that must throw an exception here

}, '/expected error message/');

Nếu mã chúng ta nhập bên trong hàm ẩn danh không ném ngoại lệ, ngoại lệ sẽ được ném.

Nếu mã chúng ta nhập bên trong hàm ẩn danh sẽ ném ra một ngoại lệ, nhưng thông báo của nó không khớp với biểu thức chính quy dự kiến, một ngoại lệ cũng sẽ bị ném.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.