PHPUnit утверждать, что было сгенерировано исключение?

338

Кто-нибудь знает, существует ли assertили что-то подобное, которое может проверить, было ли выброшено исключение в тестируемом коде?

Фелипе Алмейда
источник
2
На эти ответы: как насчет многократных утверждений в тестовой функции, и я просто ожидаю, что будет одно исключение броска? Должен ли я их разделять и помещать в независимую функцию тестирования?
Панвен Ван

Ответы:

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

waitException () PHPUnit документация

Статья автора PHPUnit содержит подробное объяснение лучших практик тестирования исключений.

Фрэнк Фармер
источник
8
Если вы используете пространства имен, вам нужно ввести полное пространство имен:$this->setExpectedException('\My\Name\Space\MyCustomException');
Alcalyn
15
Тот факт, что вы не можете назначить точную строку кода, который должен выдать, является ошибкой IMO. А неспособность проверять более одного исключения в одном тесте делает тестирование многих ожидаемых исключений действительно неуклюжим делом. Я написал реальное утверждение, чтобы попытаться решить эти проблемы.
mindplay.dk
18
К вашему сведению: по состоянию на phpunit 5.2.0 setExpectedException метод устарел, заменен на метод expectException. :)
hejdav
41
То, что не упомянуто в документации или здесь, но код, который должен вызвать исключение, должен быть вызван после expectException() . Хотя это могло быть очевидным для некоторых, для меня это было ошибкой .
Джейсон МакКрири
7
Это не очевидно из документа, но никакой код после вашей функции, которая вызывает исключение, не будет выполнен. Поэтому, если вы хотите протестировать несколько исключений в одном тестовом примере, вы не можете.
Лоран
122

Вы также можете использовать аннотацию docblock до выхода PHPUnit 9:

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

Для PHP 5.5+ (особенно с кодом пространства имен) я сейчас предпочитаю использовать ::class

Дэвид Харкнесс
источник
3
ИМО, это предпочтительный метод.
Майк Перселл,
12
@LeviMorrison - ИМХО сообщение об исключении не следует проверять, аналогично сообщениям журнала. И то, и другое считается посторонней, полезной информацией при выполнении ручной криминалистики. Ключевым моментом для проверки является тип исключения. Все, что находится за этим, слишком тесно связано с реализацией. IncorrectPasswordExceptionдолжно быть достаточно - что сообщение равно "Wrong password for bob@me.com"является вспомогательным. Добавьте к этому то, что вы хотите потратить как можно меньше времени на написание тестов, и вы начинаете понимать, насколько важными становятся простые тесты.
Дэвид Харкнесс
5
@DavidHarkness Я подумал, что кто-то поднимет это. Точно так же я бы согласился, что тестирование сообщений в целом слишком строго и жестко. Однако именно эта строгость и жесткая привязка могут (что подчеркивается целенаправленно) быть тем, что требуется в некоторых ситуациях, таких как исполнение спецификации.
Леви Моррисон
1
Я бы не смотрел в doc-блоке, чтобы понять, чего он ожидает, но я бы посмотрел на реальный тестовый код (независимо от вида теста). Это стандарт для всех других тестов; Я не вижу веских причин для того, чтобы исключения были (о боже) исключением из этого соглашения.
Kamafeather
3
Правило «не проверять сообщение» звучит правильно, если только вы не протестируете метод, который выдает один и тот же тип исключения в нескольких частях кода, с той лишь разницей, что идентификатор ошибки передается в сообщении. Ваша система может отображать сообщение для пользователя на основе сообщения об исключении (не типа исключения). В этом случае имеет значение, какое сообщение видит пользователь, следовательно, вы должны проверить сообщение об ошибке.
Ваня Д.
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
@ кен ты наверное прав. Вызов, failвероятно, принадлежит после блока catch, а не внутри try.
Фрэнк Фармер
1
Я должен понизить голос, потому что вызов failне должен быть в tryблоке. Это само по себе вызывает catchблок, выдающий ложные результаты.
Двадцать
6
Я полагаю, что причина, по которой это не работает, заключается в том, что в некоторых случаях используются все исключения catch(Exception $e). Этот метод работает очень хорошо для меня, когда я пытаюсь поймать определенные исключения:try { throw new MySpecificException; $this->fail('MySpecificException not thrown'); } catch(MySpecificException $e){}
spyle
23

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

Вставьте метод в ваш TestCase и используйте:

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

Я также сделал черту для любителей хорошего кода ..

hejdav
источник
Какой PHPUnit вы используете? Я использую PHPUnit 4.7.5, и там assertExceptionне определено. Я также не могу найти его в руководстве по PHPUnit.
физическая
2
asertExceptionМетод не является частью оригинального PHPUnit. Вы должны унаследовать PHPUnit_Framework_TestCaseкласс и вручную добавить метод, связанный в посте выше . Ваши тестовые примеры будут наследовать этот унаследованный класс.
Хейдав
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');
    });
}
тонкость
источник
Определенно лучшее решение из всех ответов! Бросьте это в черту и упакуйте это!
Домдамброджа
11

Комплексное решение

Текущие " лучшие практики " PHPUnit для тестирования исключений кажутся .. тусклыми ( документы ).

Так как я хотел больше, чем текущая expectExceptionреализация, я использовал эту черту в своих тестовых примерах. Это всего ~ 50 строк кода .

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

Библиотека

Я опубликовал эту 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();
        });
    }
}

?>
jchook
источник
4
Немного иронично, что ваш пакет для юнит-тестирования не включает юнит-тесты в репозитории.
Домдамброджия
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");
    }

}
ab_wanyama
источник
Сигнатура assertEquals()IS 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!');
    }
}

Документацию можно найти здесь .

Westy92
источник
Это неверно, потому что PHP останавливается на первом сгенерированном исключении. PHPUnit проверяет, что выброшенное исключение имеет правильный тип и говорит «тест в порядке», он даже не знает о втором исключении.
Finesse
3
/**
 * @expectedException Exception
 * @expectedExceptionMessage Amount has to be bigger then 0!
 */
public function testDepositNegative()
{
    $this->account->deposit(-7);
}

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

C Cislariu
источник
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/');

Если код, который мы вводим внутри анонимной функции, не генерирует исключение, оно будет выброшено.

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

Хауме Муссон Абад
источник