PHPUnit menegaskan bahwa pengecualian dilemparkan?

337

Adakah yang tahu apakah ada assertatau sesuatu seperti itu yang dapat menguji apakah pengecualian dilemparkan ke dalam kode yang sedang diuji?

Felipe Almeida
sumber
2
Untuk jawaban-jawaban itu: bagaimana dengan multi-pernyataan dalam suatu fungsi tes, dan saya hanya berharap untuk memiliki satu pengecualian melempar? Apakah saya HARUS memisahkannya dan meletakkannya dalam fungsi tes independen?
Panwen Wang

Jawaban:

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

expectException () dokumentasi PHPUnit

Artikel penulis PHPUnit memberikan penjelasan terperinci tentang pengujian pengecualian praktik terbaik.

Frank Farmer
sumber
8
Jika Anda menggunakan namespaces, Anda harus memasukkan namespace lengkap:$this->setExpectedException('\My\Name\Space\MyCustomException');
Alcalyn
15
Fakta bahwa Anda tidak dapat menentukan baris kode yang tepat yang diharapkan untuk dibuang, adalah IMO galat. Dan ketidakmampuan untuk menguji lebih dari satu pengecualian dalam pengujian yang sama, membuat pengujian untuk banyak pengecualian yang diharapkan benar-benar kikuk. Saya menulis pernyataan yang sebenarnya untuk mencoba menyelesaikan masalah-masalah itu.
mindplay.dk
18
FYI: pada metode phpunit 5.2.0 setExpectedException sudah usang, diganti dengan yang expectExceptionsatu. :)
hejdav
41
Apa yang tidak disebutkan dalam dokumen atau di sini, tapi kode diharapkan untuk melemparkan pengecualian kebutuhan untuk dipanggil setelah expectException() . Sementara itu mungkin sudah jelas untuk beberapa, itu adalah Gotcha bagi saya.
Jason McCreary
7
Tidak jelas dari dokumen, tetapi tidak ada kode setelah fungsi Anda yang melempar pengecualian akan dieksekusi. Jadi, jika Anda ingin menguji beberapa pengecualian dalam test case yang sama, Anda tidak bisa.
laurent
122

Anda juga dapat menggunakan anotasi docblock hingga PHPUnit 9 dirilis:

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

Untuk PHP 5.5+ (terutama dengan kode namespaced), saya sekarang lebih suka menggunakan ::class

David Harkness
sumber
3
IMO, ini adalah metode yang disukai.
Mike Purcell
12
@LeviMorrison - IMHO pesan pengecualian tidak boleh diuji, mirip dengan pesan log. Keduanya dianggap sebagai informasi asing yang membantu ketika melakukan forensik manual . Titik kunci untuk menguji adalah jenis pengecualian. Apa pun di luar itu mengikat terlalu ketat pada implementasinya. IncorrectPasswordExceptionharus cukup - bahwa pesan yang sama "Wrong password for [email protected]"adalah tambahan. Tambahkan bahwa Anda ingin menghabiskan sedikit waktu menulis tes mungkin, dan Anda mulai melihat betapa pentingnya tes sederhana menjadi.
David Harkness
5
@ Davidvidark, saya pikir seseorang akan mengungkitnya. Saya juga akan setuju bahwa pengujian pesan secara umum terlalu ketat dan ketat. Namun, keketatan dan pengikatan ketat itulah yang mungkin (ditekankan dengan sengaja) seperti yang diinginkan dalam beberapa situasi, seperti penegakan suatu spec.
Levi Morrison
1
Saya tidak akan menonton di blok dokumen untuk memahami apa yang diharapkan, tetapi saya akan melihat kode tes yang sebenarnya (terlepas dari jenis tes). Itu standar untuk semua tes lainnya; Saya tidak melihat alasan yang sah untuk Pengecualian sebagai (oh tuhan) pengecualian untuk konvensi ini.
Kamafeather
3
Aturan "jangan menguji pesan" terdengar valid, kecuali jika Anda menguji metode yang melempar tipe pengecualian yang sama di beberapa bagian kode, dengan satu-satunya perbedaan adalah id kesalahan, yang diteruskan dalam pesan. Sistem Anda mungkin menampilkan pesan kepada pengguna berdasarkan pesan Pengecualian (bukan tipe Pengecualian). Dalam hal itu, tidak masalah pesan mana yang dilihat pengguna, oleh karena itu, Anda harus menguji pesan kesalahan tersebut.
Vanja D.
34

Jika Anda menggunakan PHP 5.5+, Anda bisa menggunakan ::classresolusi untuk mendapatkan nama kelas dengan expectException/setExpectedException . Ini memberikan beberapa manfaat:

  • Nama akan sepenuhnya memenuhi syarat dengan namespace-nya (jika ada).
  • Itu memutuskan untuk stringjadi itu akan bekerja dengan versi PHPUnit.
  • Anda mendapatkan penyelesaian kode dalam IDE Anda.
  • Kompiler PHP akan memunculkan kesalahan jika Anda salah ketik nama kelas.

Contoh:

namespace \My\Cool\Package;

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

Kompilasi PHP

WrongPasswordException::class

ke

"\My\Cool\Package\WrongPasswordException"

tanpa PHPUnit menjadi lebih bijak.

Catatan : PHPUnit 5.2 diperkenalkan expectException sebagai pengganti setExpectedException.

David Harkness
sumber
32

Kode di bawah ini akan menguji pesan pengecualian dan kode pengecualian.

Penting: Ini akan gagal jika pengecualian yang diharapkan tidak dibuang juga.

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
sumber
6
$this->fail()tidak dimaksudkan untuk digunakan dengan cara ini saya tidak berpikir, setidaknya saat ini (PHPUnit 3.6.11); itu bertindak sebagai pengecualian itu sendiri. Menggunakan contoh, jika $this->fail("Expected exception not thrown")disebut, maka catchblok dipicu dan $e->getMessage()yang "diharapkan pengecualian tidak dilempar" .
ken
1
@ken Anda mungkin benar. Panggilan ke failmungkin milik setelah blok tangkap, bukan di dalam coba.
Frank Farmer
1
Saya harus downvote karena panggilan ke failtidak boleh di tryblok. Itu sendiri memicu catchblok menghasilkan hasil yang salah.
Twifty
6
Saya percaya alasan ini tidak bekerja dengan baik adalah beberapa situasi adalah bahwa itu menangkap semua pengecualian catch(Exception $e). Metode ini bekerja cukup baik untuk saya ketika saya mencoba menangkap Pengecualian tertentu:try { throw new MySpecificException; $this->fail('MySpecificException not thrown'); } catch(MySpecificException $e){}
spyle
23

Anda dapat menggunakan ekstensi assertException untuk menegaskan lebih dari satu pengecualian selama satu pengujian dijalankan.

Masukkan metode ke dalam TestCase Anda dan gunakan:

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

Saya juga membuat sifat untuk pecinta kode yang bagus ..

hejdav
sumber
PHPUnit mana yang Anda gunakan? Saya menggunakan PHPUnit 4.7.5, dan tidak ada assertExceptiondefinisi. Saya juga tidak dapat menemukannya di manual PHPUnit.
physicalattraction
2
The asertExceptionMetode bukan bagian dari PHPUnit asli. Anda harus mewarisi PHPUnit_Framework_TestCasekelas dan menambahkan metode yang ditautkan dalam posting di atas secara manual. Kasing uji Anda akan mewarisi kelas warisan ini.
hejdav
18

Cara alternatif bisa sebagai berikut:

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

Harap pastikan bahwa kelas tes Anda meluas \PHPUnit_Framework_TestCase.

Antonis Charalambous
sumber
Yang pasti gula terbanyak dalam sintaksis ini
AndrewMcLagan
13

expectExceptionMetode PHPUnit sangat merepotkan karena memungkinkan untuk menguji hanya satu pengecualian per metode pengujian.

Saya telah membuat fungsi pembantu ini untuk menyatakan bahwa beberapa fungsi melempar pengecualian:

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

Tambahkan ke kelas tes Anda dan panggil seperti ini:

public function testSomething() {
    $this->assertException(\PDOException::class, function() {
        new \PDO('bad:param');
    });
    $this->assertException(\PDOException::class, function() {
        new \PDO('foo:bar');
    });
}
Kemahiran
sumber
Pasti solusi terbaik dari semua jawaban! Membuangnya ke dalam sifat dan mengemasnya!
domdambrogia
11

Solusi Komprehensif

" Praktik terbaik " PHPUnit saat ini untuk pengujian pengecualian tampaknya .. kurang bersemangat ( dokumen ).

Karena saya menginginkan lebih dari expectExceptionimplementasi saat ini , saya membuat sifat untuk digunakan pada kasus pengujian saya. Hanya ~ 50 baris kode .

  • Mendukung banyak pengecualian per tes
  • Mendukung pernyataan yang dipanggil setelah pengecualian dilemparkan
  • Contoh penggunaan yang kuat dan jelas
  • assertSintaks standar
  • Mendukung pernyataan untuk lebih dari sekadar pesan, kode, dan kelas
  • Mendukung pernyataan terbalik, assertNotThrows
  • Mendukung Throwablekesalahan PHP 7

Perpustakaan

Saya menerbitkan AssertThrowssifat tersebut ke Github dan pembuat kemasan sehingga dapat diinstal dengan komposer.

Contoh sederhana

Hanya untuk menggambarkan semangat di balik sintaks:

<?php

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

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

Cukup rapi?


Contoh Penggunaan Lengkap

Silakan lihat di bawah untuk contoh penggunaan yang lebih komprehensif:

<?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
sumber
4
Agak ironis bahwa paket Anda untuk pengujian unit tidak termasuk tes unit dalam repo.
domdambrogia
2
@domdambrogia berkat @ jean-beguin sekarang memiliki unit test.
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
sumber
Tanda tangan dari assertEquals()adalah assertEquals(mixed $expected, mixed $actual...), terbalik seperti pada contoh Anda, jadi seharusnya$this->assertEquals("Exception message", $ex->getMessage());
Roger Campanera
7

Berikut semua pernyataan pengecualian yang dapat Anda lakukan. Perhatikan bahwa semuanya adalah opsional .

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

Dokumentasi dapat ditemukan di sini .

Westy92
sumber
Itu salah karena PHP berhenti pada pengecualian yang pertama kali dilempar. PHPUnit memeriksa bahwa eksepsi yang dilempar memiliki tipe yang benar dan mengatakan «tesnya OK», bahkan tidak tahu tentang eksepsi kedua.
Finesse
3
/**
 * @expectedException Exception
 * @expectedExceptionMessage Amount has to be bigger then 0!
 */
public function testDepositNegative()
{
    $this->account->deposit(-7);
}

Berhati-hatilah "/**", perhatikan ganda "*". Hanya menulis "**" (asterix) yang akan gagal kode Anda. Pastikan juga Anda menggunakan versi terakhir phpUnit. Dalam beberapa versi sebelumnya, phpunit @expectedException Exception tidak didukung. Saya memiliki 4.0 dan tidak berfungsi untuk saya, saya harus memperbarui ke 5.5 https://coderwall.com/p/mklvdw/install-phpunit-with-composer untuk memperbarui dengan komposer.

C Cislariu
sumber
0

Untuk PHPUnit 5.7.27 dan PHP 5.6 dan untuk menguji beberapa pengecualian dalam satu pengujian, penting untuk memaksa pengujian pengecualian. Menggunakan penanganan pengecualian saja untuk menegaskan instance Exception akan melewatkan pengujian situasi jika tidak ada pengecualian yang terjadi.

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

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

ini tesnya

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
sumber
0

PhpUnit adalah perpustakaan yang luar biasa, tetapi poin khusus ini agak membuat frustrasi. Inilah sebabnya kami dapat menggunakan pustaka opensource php-turbotesting yang memiliki metode pernyataan yang sangat nyaman untuk membantu kami menguji pengecualian. Itu ditemukan di sini:

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

Dan untuk menggunakannya, kita cukup melakukan hal berikut:

AssertUtils::throwsException(function(){

    // Some code that must throw an exception here

}, '/expected error message/');

Jika kode yang kita ketikkan di dalam fungsi anonim tidak memunculkan eksepsi, eksepsi akan dilempar.

Jika kode yang kita ketikkan di dalam fungsi anonim melempar pengecualian, tetapi pesannya tidak cocok dengan regexp yang diharapkan, pengecualian juga akan dibuang.

Jaume Mussons Abad
sumber