PHPUnit afferma che è stata generata un'eccezione?


337

Qualcuno sa se esiste una asserto qualcosa del genere che può verificare se nel codice in prova è stata generata un'eccezione?


2
A quelle risposte: che dire di multi-asserzioni in una funzione di test e mi aspetto solo di avere un'eccezione di lancio? Devo separarli e mettere quello in una funzione di test indipendente?
Panwen Wang,

Risposte:


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

awaException () Documentazione PHPUnit

L'articolo dell'autore PHPUnit fornisce spiegazioni dettagliate sul test delle migliori pratiche di eccezioni.


8
Se usi gli spazi dei nomi, devi inserire l'intero spazio dei nomi:$this->setExpectedException('\My\Name\Space\MyCustomException');
Alcalyn,

15
Il fatto che non sia possibile designare la precisa riga di codice che si prevede di generare è un errore IMO. E l'incapacità di testare più di un'eccezione nello stesso test, rende il test per molte eccezioni previste un affare davvero goffo. Ho scritto un'affermazione vera e propria per cercare di risolvere quei problemi.
mindplay.dk,

18
Cordiali saluti: a partire dal metodo phpunit 5.2.0 setExpectedException è deprecato, sostituito con expectExceptionquello. :)
hejdav

41
Ciò che non è menzionato nei documenti o qui, ma il codice che dovrebbe generare un'eccezione deve essere chiamato dopo expectException() . Anche se potrebbe essere stato ovvio per alcuni, è stato un gotcha per me.
Jason McCreary,

7
Non è ovvio dal documento, ma nessun codice dopo la tua funzione che genera un'eccezione verrà eseguito. Pertanto, se si desidera verificare più eccezioni nello stesso caso di test, non è possibile.
laurent,

122

Puoi anche usare un'annotazione docblock fino al rilascio di PHPUnit 9:

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

Per PHP 5.5+ (specialmente con il codice con spazio dei nomi), ora preferisco usare ::class


3
IMO, questo è il metodo preferito.
Mike Purcell,

12
@LeviMorrison - IMHO il messaggio di eccezione non deve essere testato, analogamente ai messaggi di registro. Entrambi sono considerati informazioni estranee e utili quando si eseguono analisi forensi manuali . Il punto chiave da testare è il tipo di eccezione. Qualsiasi cosa oltre a ciò è troppo vincolante per l'implementazione. IncorrectPasswordExceptiondovrebbe essere sufficiente: il messaggio uguale "Wrong password for bob@me.com"è secondario. Aggiungete a ciò che volete dedicare il minor tempo possibile alla scrittura dei test e inizia a vedere quanto diventano importanti i test semplici.
David Harkness,

5
@DavidHarkness Ho pensato che qualcuno l'avrebbe sollevato. Allo stesso modo, concordo sul fatto che testare i messaggi in generale sia troppo severo e rigoroso. Tuttavia, è quel rigore e vincolo stretto che può (enfatizzato intenzionalmente) essere ciò che si desidera in alcune situazioni, come l'applicazione di una specifica.
Levi Morrison,

1
Non guarderei in un blocco di documenti per capire cosa si aspettava, ma guarderei il vero codice di test (indipendentemente dal tipo di test). Questo è lo standard per tutti gli altri test; Non vedo validi motivi per cui Eccezioni sia (o dio) un'eccezione a questa convenzione.
Kamafeather,

3
La regola "non testare il messaggio" sembra valida, a meno che non si verifichi un metodo che genera lo stesso tipo di eccezione in più parti del codice, con l'unica differenza che è l'id di errore, che viene passato nel messaggio. Il sistema potrebbe visualizzare un messaggio per l'utente in base al messaggio di eccezione (non al tipo di eccezione). In tal caso, non importa quale messaggio viene visualizzato dall'utente, quindi è necessario testare il messaggio di errore.
Vanja D.

34

Se usi PHP 5.5+, puoi usare la ::classrisoluzione per ottenere il nome della classe con expectException/setExpectedException . Ciò offre numerosi vantaggi:

  • Il nome sarà pienamente qualificato con il suo spazio dei nomi (se presente).
  • Si risolve in un stringmodo che funzionerà con qualsiasi versione di PHPUnit.
  • Ottieni il completamento del codice nel tuo IDE.
  • Il compilatore PHP emetterà un errore se si digita male il nome della classe.

Esempio:

namespace \My\Cool\Package;

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

Compilazioni PHP

WrongPasswordException::class

in

"\My\Cool\Package\WrongPasswordException"

senza PHPUnit essere il più saggio.

Nota : PHPUnit 5.2 introdotto expectException in sostituzione di setExpectedException.


32

Il codice seguente testerà il messaggio di eccezione e il codice di eccezione.

Importante: fallirà se non viene generata anche l'eccezione prevista.

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()non è pensato per essere usato in questo modo non credo, almeno non attualmente (PHPUnit 3.6.11); agisce come un'eccezione stessa. Utilizzando l'esempio, se $this->fail("Expected exception not thrown")viene chiamato, il catchblocco viene attivato ed $e->getMessage()è "Eccezione prevista non generata" .
Ken,

1
@ken probabilmente hai ragione. La chiamata a failprobabilmente appartiene dopo il blocco catch, non all'interno del tentativo.
Frank Farmer,

1
Devo effettuare il downvote perché la chiamata a failnon dovrebbe essere nel tryblocco. Di per sé innesca il catchblocco producendo risultati falsi.
Twifty,

6
Credo che il motivo per cui questo non funzioni bene sia che alcune situazioni siano in grado di cogliere tutte le eccezioni catch(Exception $e). Questo metodo funziona abbastanza bene per me quando provo a cogliere specifiche eccezioni:try { throw new MySpecificException; $this->fail('MySpecificException not thrown'); } catch(MySpecificException $e){}
spyle

23

È possibile utilizzare l'estensione assertException per affermare più di un'eccezione durante un'esecuzione di test.

Inserisci il metodo nel tuo TestCase e usa:

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

Ho anche fatto un tratto per gli amanti del bel codice ..


Quale PHPUnit stai usando? Sto usando PHPUnit 4.7.5 e non assertExceptionè definito. Inoltre non riesco a trovarlo nel manuale di PHPUnit.
fisica il

2
Il asertExceptionmetodo non fa parte di PHPUnit originale. È necessario ereditare la PHPUnit_Framework_TestCaseclasse e aggiungere manualmente il metodo collegato in post sopra . I casi di test erediteranno quindi questa classe ereditata.
hejdav,

18

Un modo alternativo può essere il seguente:

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

Assicurati che la tua classe di test si estenda \PHPUnit_Framework_TestCase.


Sicuramente la maggior quantità di zucchero in questa sintassi
AndrewMcLagan

13

Il expectExceptionmetodo PHPUnit è molto scomodo perché consente di testare solo un'eccezione per un metodo di test.

Ho creato questa funzione di supporto per affermare che alcune funzioni generano un'eccezione:

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

Aggiungilo alla tua classe di test e chiama in questo modo:

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

Sicuramente la migliore soluzione tra tutte le risposte! Gettalo in un tratto e impacchettalo!
domdambrogia,

11

Soluzione completa

Le attuali " migliori pratiche " di PHPUnit per i test delle eccezioni sembrano ... poco brillanti ( documenti ).

Dal momento che volevo qualcosa di più rispetto all'attuale expectExceptionimplementazione, ho creato un tratto da utilizzare nei miei casi di test. Sono solo ~ 50 righe di codice .

  • Supporta più eccezioni per test
  • Supporta asserzioni chiamate dopo che è stata generata l'eccezione
  • Esempi di utilizzo chiari e affidabili
  • assertSintassi standard
  • Supporta asserzioni per più di un semplice messaggio, codice e classe
  • Supporta l'affermazione inversa, assertNotThrows
  • Supporta Throwableerrori PHP 7

Biblioteca

Ho pubblicato il AssertThrowstratto su Github e packagist in modo che possa essere installato con il compositore.

Esempio semplice

Giusto per illustrare lo spirito dietro la sintassi:

<?php

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

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

Piuttosto pulito?


Esempio di utilizzo completo

Vedi sotto per un esempio di utilizzo più completo:

<?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
Un po 'ironico che il pacchetto per i test unitari non includa i test unitari nel repository.
domdambrogia,

2
@domdambrogia grazie a @ jean-beguin ora ha test unitari.
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");
    }

}

La firma di assertEquals()è assertEquals(mixed $expected, mixed $actual...), al contrario come nel tuo esempio, quindi dovrebbe essere$this->assertEquals("Exception message", $ex->getMessage());
Roger Campanera il

7

Ecco tutte le affermazioni sulle eccezioni che puoi fare. Si noti che tutti sono opzionali .

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

La documentazione è disponibile qui .


Non è corretto perché PHP si arresta sulla prima eccezione generata. PHPUnit verifica che l'eccezione generata abbia il tipo corretto e dice "il test è OK", non è nemmeno a conoscenza della seconda eccezione.
Finesse,

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

Fai molta attenzione "/**", nota il doppio "*". Scrivere solo "**" (asterix) non riuscirà il tuo codice. Assicurati anche di utilizzare l'ultima versione di phpUnit. In alcune versioni precedenti di phpunit @expectedException l'eccezione non è supportata. Avevo 4.0 e non ha funzionato per me, ho dovuto aggiornare a 5.5 https://coderwall.com/p/mklvdw/install-phpunit-with-composer per aggiornare con il compositore.


0

Per PHPUnit 5.7.27 e PHP 5.6 e per testare più eccezioni in un test, era importante forzare il test delle eccezioni. L'uso della sola gestione delle eccezioni per affermare l'istanza di Exception salterà il test della situazione se non si verifica alcuna eccezione.

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

ecco il test

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 è una libreria straordinaria, ma questo punto specifico è un po 'frustrante. Questo è il motivo per cui possiamo usare la libreria open source turbotesting-php che ha un metodo di asserzione molto conveniente per aiutarci a testare le eccezioni. Si trova qui:

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

E per usarlo, faremmo semplicemente quanto segue:

AssertUtils::throwsException(function(){

    // Some code that must throw an exception here

}, '/expected error message/');

Se il codice che digitiamo all'interno della funzione anonima non genera un'eccezione, verrà generata un'eccezione.

Se il codice che digitiamo all'interno della funzione anonima genera un'eccezione, ma il suo messaggio non corrisponde al regexp previsto, verrà generata anche un'eccezione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.