PHPUnitは例外がスローされたと主張しますか?


337

assertテストされているコードで例外がスローされたかどうかをテストできるものがあるかどうか誰かが知っていますか?


2
それらの答えに対して:テスト関数のマルチアサーションはどうですか、そして私はただ1つの例外をスローすることを期待していますか?それらを分離して、独立したテスト機能に配置する必要がありますか?
Panwen Wang 2016

回答:


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

15
スローされることが予想される正確なコード行を指定できないという事実は、エラーIMOです。また、同じテストで複数の例外をテストできないため、予想される多くの例外のテストは非常に不格好です。私はそれらの問題を解決しようとする実際の主張を書きました。
mindplay.dk

18
参考phpunit 5.2.0以降、setExpectedExceptionメソッドは廃止され、メソッドに置き換えられましたexpectException。:)
hejdav 2016年

41
ドキュメントまたはここで言及されていないものですが、例外をスローすることが予想されるコードは、後で 呼び出す必要がありますexpectException()。一部の人にはそれが自明だったかもしれませんが、それは私にとって落とし穴でした。
Jason McCreary 2016年

7
ドキュメントからは明らかではありませんが、関数の後に例外をスローするコードは実行されません。したがって、同じテストケースで複数の例外をテストする場合はできません。
ローレント

122

PHPUnit 9がリリースされるまで、docblockアノテーションを使用することもできます。

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

PHP 5.5以降(特に名前空間のあるコード)では、今は ::class


3
IMO、これは推奨される方法です。
Mike Purcell、

12
@LeviMorrison- ログメッセージと同様に、例外メッセージはテストしないでください。手動フォレンジックを実行する場合、どちらも無関係で有用な情報と見なされます。テストのポイントは、例外のタイプです。それ以上のものは、実装にあまりにも強く結合しています。IncorrectPasswordExceptionメッセージが等しいこと"Wrong password for bob@me.com"は補助的である必要があります。それに加えて、テストの記述にできるだけ時間をかけたくない場合、シンプルなテストがいかに重要になるかがわかります。
David Harkness 2013

5
@DavidHarkness誰かがそれを持ち出すと思った。同様に、一般的にメッセージのテストは厳しすぎ、厳しすぎることに同意します。ただし、仕様の実施など、状況によっては(意図的に強調されて)必要となる可能性があるのは、その厳密さと緊密なバインディングです。
Levi Morrison、

1
doc-blockを見てそれが何を期待しているかを理解することはしませんが、実際のテストコードを調べます(テストの種類に関係なく)。それが他のすべてのテストの標準です。例外がこの規則の例外となる正当な理由がわかりません。
Kamafeather 2015年

3
コードの複数の部分で同じ例外タイプをスローするメソッドをテストしない限り、「メッセージをテストしない」ルールは有効に聞こえます。唯一の違いは、メッセージで渡されるエラーIDです。システムは、(例外タイプではなく)例外メッセージに基づいてユーザーにメッセージを表示する場合があります。その場合、ユーザーにどのメッセージが表示されるかは重要なので、エラーメッセージをテストする必要があります。
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()ている「例外がスローされません期待します」

1
@kenあなたはおそらく正しいです。への呼び出しは、try内ではなく、catchブロックのfail属している可能性があります。
フランクファーマー

1
への呼び出しfailtryブロックにすべきではないので、私は反対票を投じなければなりません。それ自体がcatchブロックをトリガーし、誤った結果を生成します。
Twifty

6
これがうまくいかない理由は、いくつかの状況がですべての例外をキャッチしているためだと思いますcatch(Exception $e)。私は特定の例外をキャッチしようとすると、この方法は私のために非常によく動作します:try { throw new MySpecificException; $this->fail('MySpecificException not thrown'); } catch(MySpecificException $e){}
spyle

23

assertException拡張を使用して、1回のテスト実行中に複数の例外をアサートできます。

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クラスを継承し、上記のpostでリンクされたメソッドを手動で追加する必要があります。テストケースは、この継承されたクラスを継承します。
hejdav

18

別の方法は次のようになります。

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

テストクラスの範囲を確認してください\PHPUnit_Framework_TestCase


確かにこの構文で最も砂糖
AndrewMcLagan

13

PHPUnit expectExceptionメソッドは、テストメソッドごとに1つの例外のみをテストできるため、非常に不便です。

このヘルパー関数を作成して、一部の関数が例外をスローすることを表明しました。

/**
 * 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に公開して、composerでインストールできるようにしました。

簡単な例

構文の背後にある精神を説明するためだけに:

<?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()ありassertEquals(mixed $expected, mixed $actual...)、それがあるべきよう、あなたの例のように、逆$this->assertEquals("Exception message", $ex->getMessage());
ロジャー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は、スローされた例外のタイプが正しいことを確認し、「テストはOK」と表示します。2番目の例外についても認識していません。
Finesse

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

に非常に注意してください"/**"。二重の「*」に注意してください。"**"(アスタリスク)のみを書き込むと、コードが失敗します。また、phpUnitの最新バージョンを使用していることを確認してください。以前のバージョンのphpunitでは、@ expectedException例外がサポートされていません。私は4.0を持っていましたが、うまくいきませんでした。composerで更新するには、https://coderwall.com/p/mklvdw/install-phpunit-with-composerを5.5 に更新する必要がありました。


0

PHPUnit 5.7.27とPHP 5.6では、1つのテストで複数の例外をテストするために、例外テストを強制することが重要でした。例外処理のみを使用して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/');

無名関数の内部で入力するコードが例外をスローしない場合、例外がスローされます。

無名関数の内部で入力したコードが例外をスローしても、そのメッセージが予期される正規表現と一致しない場合は、例外もスローされます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.