PHPUnit behaupten, dass eine Ausnahme ausgelöst wurde?

337

Weiß jemand, ob es eine assertoder eine ähnliche gibt, die testen kann, ob im getesteten Code eine Ausnahme ausgelöst wurde?

Felipe Almeida
quelle
2
Zu diesen Antworten: Was ist mit Multi-Assertions in einer Testfunktion, und ich erwarte nur eine Ausnahme von einem Wurf? MUSS ich sie trennen und in eine unabhängige Testfunktion versetzen?
Panwen Wang

Antworten:

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

erwartete EXception () PHPUnit-Dokumentation

Der Artikel des PHPUnit-Autors enthält detaillierte Erläuterungen zum Testen der Best Practices für Ausnahmen.

Frank Farmer
quelle
7
Wenn Sie Namespaces verwenden, müssen Sie den vollständigen Namespace $this->setExpectedException('\My\Name\Space\MyCustomException');
eingeben
15
Die Tatsache, dass Sie nicht die genaue Codezeile angeben können, die erwartet wird, ist ein Fehler IMO. Und die Unfähigkeit, im selben Test auf mehr als eine Ausnahme zu testen, macht das Testen auf viele erwartete Ausnahmen zu einer wirklich klobigen Angelegenheit. Ich habe eine tatsächliche Behauptung geschrieben , um zu versuchen, diese Probleme zu lösen.
mindplay.dk
18
Zu Ihrer Information : Ab phpunit 5.2.0 ist diesetExpectedException Methode veraltet und wird durch die expectExceptionersetzt. :)
hejdav
41
Was ist nicht in der Dokumentation erwähnt oder hier, aber der Code erwartet eine Ausnahme Bedürfnisse zu werfen aufgerufen werden , nach expectException() . Während es für manche offensichtlich gewesen sein mag, war es ein Gotcha für mich.
Jason McCreary
7
Es ist aus dem Dokument nicht ersichtlich, aber nach Ihrer Funktion, die eine Ausnahme auslöst, wird kein Code ausgeführt. Wenn Sie also mehrere Ausnahmen im selben Testfall testen möchten, können Sie dies nicht.
Laurent
122

Sie können auch eine Docblock-Annotation verwenden, bis PHPUnit 9 veröffentlicht wird:

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

Für PHP 5.5+ (insbesondere mit Namespace-Code) bevorzuge ich jetzt die Verwendung ::class

David Harkness
quelle
3
IMO, dies ist die bevorzugte Methode.
Mike Purcell
12
@LeviMorrison - IMHO die Ausnahme Nachricht sollte nicht getestet werden, ähnlich Nachrichten zu protokollieren. Beide werden bei der Durchführung der manuellen Forensik als irrelevante Informationen angesehen . Der wichtigste zu testende Punkt ist die Art der Ausnahme. Alles darüber hinaus ist zu eng an die Implementierung gebunden. IncorrectPasswordExceptionsollte ausreichen - dass die Nachricht gleich "Wrong password for [email protected]"ist, ist ergänzend. Fügen Sie hinzu, dass Sie so wenig Zeit wie möglich mit dem Schreiben von Tests verbringen möchten, und Sie beginnen zu sehen, wie wichtig einfache Tests werden.
David Harkness
5
@ DavidHarkness Ich dachte, jemand würde das ansprechen. Ebenso würde ich zustimmen, dass das Testen von Nachrichten im Allgemeinen zu streng und streng ist. Es ist jedoch diese Strenge und enge Bindung, die (absichtlich hervorgehoben) in einigen Situationen, wie der Durchsetzung einer Spezifikation, gewünscht sein kann.
Levi Morrison
1
Ich würde nicht in einem Dokumentblock nachsehen, um zu verstehen, was erwartet wird, aber ich würde mir den tatsächlichen Testcode ansehen (unabhängig von der Art des Tests). Das ist der Standard für alle anderen Tests. Ich sehe keine triftigen Gründe dafür, dass Ausnahmen (oh Gott) eine Ausnahme von dieser Konvention sind.
Kamafeather
3
Die Regel "Nachricht nicht testen" klingt gültig, es sei denn, Sie testen eine Methode, die denselben Ausnahmetyp in mehreren Codeteilen auslöst. Der einzige Unterschied besteht in der Fehler-ID, die in der Nachricht übergeben wird. Ihr System zeigt dem Benutzer möglicherweise eine Nachricht basierend auf der Ausnahmemeldung an (nicht dem Ausnahmetyp). In diesem Fall spielt es eine Rolle, welche Meldung der Benutzer sieht. Daher sollten Sie die Fehlermeldung testen.
Vanja D.
34

Wenn Sie mit PHP 5.5+ arbeiten, können Sie die ::classAuflösung verwenden , um den Namen der Klasse mit expectException/setExpectedException abzurufen . Dies bietet mehrere Vorteile:

  • Der Name wird mit seinem Namespace (falls vorhanden) vollständig qualifiziert.
  • Es wird in a aufgelöst, stringsodass es mit jeder Version von PHPUnit funktioniert.
  • Sie erhalten eine Code-Vervollständigung in Ihrer IDE.
  • Der PHP-Compiler gibt einen Fehler aus, wenn Sie den Klassennamen falsch eingeben.

Beispiel:

namespace \My\Cool\Package;

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

PHP kompiliert

WrongPasswordException::class

in

"\My\Cool\Package\WrongPasswordException"

ohne dass PHPUnit klüger ist.

Hinweis : PHPUnit 5.2 wurde expectException als Ersatz für eingeführt setExpectedException.

David Harkness
quelle
32

Der folgende Code testet die Ausnahmemeldung und den Ausnahmecode.

Wichtig: Es schlägt fehl, wenn die erwartete Ausnahme nicht ebenfalls ausgelöst wird.

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());
}
Farid Movsumov
quelle
6
$this->fail()soll nicht so verwendet werden, glaube ich nicht, zumindest momentan nicht (PHPUnit 3.6.11); es fungiert selbst als Ausnahme. Mit Ihrem Beispiel, wenn $this->fail("Expected exception not thrown")aufgerufen wird, dann wird der catchBlock wird ausgelöst und $e->getMessage()wird „Erwartet keine Ausnahme ausgelöst“ .
Ken
1
@ken du hast wahrscheinlich recht. Der Aufruf von failgehört wahrscheinlich nach dem catch-Block, nicht innerhalb des Try.
Frank Farmer
1
Ich muss abstimmen, weil der Anruf an failnicht im tryBlock sein sollte. Es löst an sich den catchBlock aus, der falsche Ergebnisse erzeugt.
Twifty
6
Ich glaube, der Grund, warum dies nicht gut funktioniert, ist, dass in einigen Situationen alle Ausnahmen erfasst werden catch(Exception $e). Diese Methode funktioniert ganz gut für mich, wenn ich versuche, bestimmte Ausnahmen zu fangen:try { throw new MySpecificException; $this->fail('MySpecificException not thrown'); } catch(MySpecificException $e){}
Spyle
23

Mit der Erweiterung assertException können Sie während einer Testausführung mehr als eine Ausnahme aktivieren .

Fügen Sie die Methode in Ihren TestCase ein und verwenden Sie:

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

Ich habe auch ein Merkmal für Liebhaber von nettem Code gemacht.

hejdav
quelle
Welche PHPUnit verwenden Sie? Ich verwende PHPUnit 4.7.5 und es assertExceptionist nicht definiert. Ich kann es auch nicht im PHPUnit-Handbuch finden.
körperliche
2
Die asertExceptionMethode ist nicht Teil der ursprünglichen PHPUnit. Sie müssen die PHPUnit_Framework_TestCaseKlasse erben und die im obigen Beitrag verknüpfte Methode manuell hinzufügen . Ihre Testfälle erben dann diese geerbte Klasse.
Hejdav
18

Ein alternativer Weg kann der folgende sein:

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

Bitte stellen Sie sicher, dass Ihre Testklasse erweitert ist \PHPUnit_Framework_TestCase.

Antonis Charalambous
quelle
Mit Sicherheit der meiste Zucker in dieser Syntax
AndrewMcLagan
13

Die PHPUnit- expectExceptionMethode ist sehr unpraktisch, da nur eine Ausnahme pro Testmethode getestet werden kann.

Ich habe diese Hilfsfunktion erstellt, um zu behaupten, dass eine Funktion eine Ausnahme auslöst:

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

Fügen Sie es Ihrer Testklasse hinzu und rufen Sie folgendermaßen auf:

public function testSomething() {
    $this->assertException(\PDOException::class, function() {
        new \PDO('bad:param');
    });
    $this->assertException(\PDOException::class, function() {
        new \PDO('foo:bar');
    });
}
Finesse
quelle
Auf jeden Fall die beste Lösung aus allen Antworten! Wirf es in eine Eigenschaft und verpacke es!
Domdambrogia
11

Umfassende Lösung

Die aktuellen " Best Practices " von PHPUnit für Ausnahmetests scheinen .. glanzlos ( docs ).

Da ich mehr als die aktuelle expectExceptionImplementierung wollte, habe ich eine Eigenschaft für meine Testfälle erstellt. Es sind nur ~ 50 Codezeilen .

  • Unterstützt mehrere Ausnahmen pro Test
  • Unterstützt Zusicherungen, die aufgerufen werden, nachdem die Ausnahme ausgelöst wurde
  • Robuste und klare Anwendungsbeispiele
  • Standard assert Syntax
  • Unterstützt Zusicherungen für mehr als nur Nachricht, Code und Klasse
  • Unterstützt inverse Behauptung, assertNotThrows
  • Unterstützt PHP 7- ThrowableFehler

Bibliothek

Ich habe das AssertThrowsMerkmal für Github und Packagist veröffentlicht, damit es mit Composer installiert werden kann.

Einfaches Beispiel

Nur um den Geist hinter der Syntax zu veranschaulichen:

<?php

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

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

Ziemlich ordentlich?


Beispiel für die vollständige Verwendung

Im Folgenden finden Sie ein umfassenderes Anwendungsbeispiel:

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

?>
jchook
quelle
4
Ein wenig ironisch, dass Ihr Paket für Unit-Tests keine Unit-Tests im Repo enthält.
Domdambrogia
2
@domdambrogia dank @ jean-beguin hat es jetzt Unit-Tests.
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");
    }

}
ab_wanyama
quelle
Die Unterschrift von assertEquals()ist assertEquals(mixed $expected, mixed $actual...), umgekehrt wie in Ihrem Beispiel, so sollte es sein$this->assertEquals("Exception message", $ex->getMessage());
Roger Campanera
7

Hier finden Sie alle Ausnahmen, die Sie ausführen können. Beachten Sie, dass alle optional sind .

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

Dokumentation finden Sie hier .

Westy92
quelle
Es ist falsch, weil PHP bei der ersten ausgelösten Ausnahme stoppt. PHPUnit prüft, ob die ausgelöste Ausnahme den richtigen Typ hat und sagt «der Test ist in Ordnung», es weiß nicht einmal über die zweite Ausnahme Bescheid.
Finesse
3
/**
 * @expectedException Exception
 * @expectedExceptionMessage Amount has to be bigger then 0!
 */
public function testDepositNegative()
{
    $this->account->deposit(-7);
}

Seien Sie sehr vorsichtig "/**", beachten Sie das doppelte "*". Wenn Sie nur "**" (Sternchen) schreiben, schlägt Ihr Code fehl. Stellen Sie außerdem sicher, dass Sie die letzte Version von phpUnit verwenden. In einigen früheren Versionen von phpunit @expectedException wird Exception nicht unterstützt. Ich hatte 4.0 und es funktionierte nicht für mich. Ich musste auf 5.5 https://coderwall.com/p/mklvdw/install-phpunit-with-composer aktualisieren, um mit Composer zu aktualisieren.

C Cislariu
quelle
0

Für PHPUnit 5.7.27 und PHP 5.6 und um mehrere Ausnahmen in einem Test zu testen, war es wichtig, den Ausnahmetest zu erzwingen. Wenn Sie nur die Ausnahmebehandlung verwenden, um die Instanz der Ausnahme zu aktivieren, wird das Testen der Situation übersprungen, wenn keine Ausnahme auftritt.

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

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

Hier ist der Test

class FunctionTest extends \PHPUnit_Framework_TestCase{

   public function testException(){

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

   }

}
sami klah
quelle
0

PhpUnit ist eine erstaunliche Bibliothek, aber dieser spezielle Punkt ist ein bisschen frustrierend. Aus diesem Grund können wir die OpenSource-Bibliothek turbotesting-php verwenden, die über eine sehr praktische Assertionsmethode verfügt, mit der wir Ausnahmen testen können. Es ist hier zu finden:

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

Und um es zu verwenden, würden wir einfach Folgendes tun:

AssertUtils::throwsException(function(){

    // Some code that must throw an exception here

}, '/expected error message/');

Wenn der Code, den wir in die anonyme Funktion eingeben, keine Ausnahme auslöst, wird eine Ausnahme ausgelöst.

Wenn der Code, den wir in die anonyme Funktion eingeben, eine Ausnahme auslöst, die Nachricht jedoch nicht mit dem erwarteten regulären Ausdruck übereinstimmt, wird auch eine Ausnahme ausgelöst.

Jaume Mussons Abad
quelle