PHPUnit стверджує, що виняток було кинуто?


337

Хтось знає, чи є assertщось подібне, яке може перевірити, чи був викид у тестований код?


2
На ці відповіді: як щодо багатостверджень у тестовій функції, і я просто розраховую на те, що у вас є виключення з одним кидком? Чи маю я їх відокремити та поставити цю функцію в незалежну тестову функцію?
Панвен Ван

Відповіді:


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

очікуємоException () документації PHPUnit

Стаття автора PHPUnit надає детальне пояснення щодо тестування найкращих практик винятків.


8
Якщо ви користуєтесь просторами імен, вам потрібно ввести повний простір імен:$this->setExpectedException('\My\Name\Space\MyCustomException');
Алькалін

15
Те, що ви не можете призначити точний рядок коду, який очікується, буде кинутим, - помилка IMO. І неможливість перевірити більше одного винятку в одному і тому ж тесті, робить тестування на багато очікуваних винятків справді незграбною справою. Я написав фактичне твердження, щоб спробувати вирішити ці проблеми.
mindplay.dk

18
FYI: метод phpunit 5.2.0 setExpectedException застарілий, замінений на expectExceptionодин. :)
hejdav

41
Те, що не згадується в документах або тут, але код, за який очікується виняток, повинен бути викликаний після expectException() . Хоча для когось це могло бути очевидним, для мене це було придурком .
Джейсон МакКрірі

7
Це не очевидно з doc, але жоден код після вашої функції, який видає виняток, не буде виконаний. Тож якщо ви хочете перевірити кілька винятків у одному тестовому випадку, ви не можете.
Лоран

122

Ви також можете використовувати анотацію docblock, поки PHPUnit 9 не вийде:

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

Для PHP 5.5+ (особливо з кодом простору), я вважаю за краще використовувати ::class


3
ІМО, це кращий метод.
Майк Перселл

12
@LeviMorrison - IMHO повідомлення про виключення не слід перевіряти, як і повідомлення журналів. Обидва вважаються стороною корисною інформацією під час виконання ручних криміналістичних досліджень. Ключовим моментом для тесту є тип виключення. Все, що є поза цим, занадто щільно пов'язане з реалізацією. IncorrectPasswordExceptionмає бути достатньо - щоб повідомлення було рівним "Wrong password for bob@me.com"допоміжним. Додайте до цього, що ви хочете витратити якомога менше часу на написання тестів, і ви починаєте бачити, наскільки важливими стають прості тести.
Девід Харкнесс

5
@DavidHarkness я подумав, що хтось підніме це. Так само я б погодився, що тестування повідомлень загалом занадто суворе і жорстке. Однак саме суворість і чітка обов'язковість можуть бути (цілеспрямовано підкреслено) тим, що потрібно в деяких ситуаціях, наприклад, примусовому виконанні специфікації.
Леві Моррісон

1
Я б не дивився в doc-блок, щоб зрозуміти, чого він очікував, але я би подивився на власне тестовий код (незалежно від виду тесту). Це стандарт для всіх інших тестів; Я не бачу поважних причин для того, щоб Винятки були (о боже) винятком з цієї конвенції.
Камафезер

3
Правило "не тестувати повідомлення" звучить дійсно, якщо ви не протестуєте метод, який передає один і той же тип винятку в декількох частинах коду, єдиною різницею є ідентифікатор помилки, переданий у повідомленні. Ваша система може відображати повідомлення користувачеві на основі повідомлення «Виняток» (не типу «Виняток»). У цьому випадку не має значення, яке повідомлення бачить користувач, отже, слід перевірити повідомлення про помилку.
Vanja D.

34

Якщо ви працюєте на PHP 5.5+, ви можете використовувати ::classроздільну здатність, щоб отримати ім'я класу за допомогою expectException/setExpectedException . Це дає кілька переваг:

  • Ім'я буде повністю кваліфіковане з його простором імен (за наявності).
  • Він вирішує 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 ти мабуть прав. Виклик, failймовірно, належить після блоку лову, а не всередині спроби.
Френк Фермер

1
Мені належить подати заявку, тому що виклик не failповинен бути в tryблоці. Це саме по собі запускає catchблок, що дає помилкові результати.
Двадцять

6
Я вважаю, що причина цього не спрацьовує - це ситуація в тому, що вона охоплює всі винятки catch(Exception $e). Цей метод для мене працює досить добре, коли я намагаюся зловити конкретні винятки:try { throw new MySpecificException; $this->fail('MySpecificException not thrown'); } catch(MySpecificException $e){}
шпигуйте

23

Ви можете використовувати розширення assertException, щоб стверджувати більше ніж один виняток під час одного тестового виконання.

Вставте метод у свій 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.
physicalattraction

2
asertExceptionМетод не є частиною оригінального PHPUnit. Ви повинні успадкувати PHPUnit_Framework_TestCaseклас і додати метод, пов'язаний у публікації вище, вручну. Потім ваші тестові випадки успадкують цей успадкований клас.
hejdav

18

Альтернативним способом може бути такий:

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

Ласка , переконайтеся , що ваші екстентів тестування класу \PHPUnit_Framework_TestCase.


Напевно найбільше цукру в цьому синтаксисі
AndrewMcLagan

13

Метод PHPUnit expectExceptionдуже незручний, оскільки дозволяє перевірити лише один виняток за тестовий метод.

Я зробив цю помічну функцію, щоб стверджувати, що деяка функція кидає виняток:

/**
 * 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 для тестування винятків здаються .. невмілими ( документи ).

Оскільки я хотів більше, ніж теперішня expectExceptionреалізація, я сформулював особливості використання у своїх тестових випадках. Це лише ~ 50 рядків коду .

  • Підтримує кілька винятків за тест
  • Підтримує твердження, викликані після скидання виключення
  • Надійні та чіткі приклади використання
  • Стандартний assertсинтаксис
  • Підтримує твердження для більш ніж просто повідомлення, коду та класу
  • Підтримує обернене твердження, assertNotThrows
  • Підтримує Throwableпомилки PHP 7

Бібліотека

Я опублікував цю AssertThrowsхарактеристику Github та пакувальнику, щоб її можна було встановити разом із композитором.

Простий приклад

Просто для ілюстрації духу за синтаксисом:

<?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());
Роджер Кампанера

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 перевіряє, що викинуте виняток має правильний тип, і каже «тест нормальний», він навіть не знає про друге виняток.
Фінес

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

Будьте дуже обережні "/**", помічайте подвійне "*". Якщо записати лише "**" (зірочка), ваш код не вдасться. Також переконайтеся, що ви використовуєте останню версію phpUnit. У деяких попередніх версіях phpunit @expectedException Виняток не підтримується. У мене було 4.0, і це не спрацювало для мене, мені довелося оновити до 5,5 https://coderwall.com/p/mklvdw/install-phpunit-with-composer, щоб оновити з композитором.


0

Для PHPUnit 5.7.27 та PHP 5.6 та для тестування декількох винятків в одному тесті важливо було застосувати тестування виключень. Використовуючи лише обробку винятків для затвердження екземпляра 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/');

Якщо код, який ми вводимо всередині анонімної функції, не виключає виключення, буде викинуто виняток.

Якщо код, який ми вводимо всередині анонімної функції, видає виняток, але його повідомлення не відповідає очікуваному регулярному вираженню, також буде викинуто виняток.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.